Initial commit

This commit is contained in:
kromskii2 committed 2024-11-13 15:53:05 +07:00
commit dfbb21800b
5220 files changed
+368059

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
version: 2.1
jobs:
test:
docker:
- image: circleci/node:11.5.0
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run: yarn install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- run: yarn test
release:
docker:
- image: cimg/node:18.12.1
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run: yarn install
- run: npx semantic-release
workflows:
version: 2.1
test_and_release:
jobs:
- test
- release:
context:
- test-runner:npm-release
requires:
- test
filters:
branches:
only:
- master
+15
View File
@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2019, Nicolai Kamenzky, Ty Abonil, and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+760
View File
@@ -0,0 +1,760 @@
<a href="http://promisesaplus.com/">
<img src="https://promises-aplus.github.io/promises-spec/assets/logo-small.png" align="right" alt="Promises/A+ logo" />
</a>
# Request-Promise
[![Gitter](https://img.shields.io/badge/gitter-join_chat-blue.svg?style=flat-square&maxAge=2592000)](https://gitter.im/request/request-promise?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://img.shields.io/travis/request/request-promise/master.svg?style=flat-square&maxAge=2592000)](https://travis-ci.org/request/request-promise)
[![Coverage Status](https://img.shields.io/coveralls/request/request-promise.svg?style=flat-square&maxAge=2592000)](https://coveralls.io/r/request/request-promise)
[![Dependency Status](https://img.shields.io/david/request/request-promise.svg?style=flat-square&maxAge=2592000)](https://david-dm.org/request/request-promise)
[![Known Vulnerabilities](https://snyk.io/test/npm/request-promise/badge.svg?style=flat-square&maxAge=2592000)](https://snyk.io/test/npm/request-promise)
# Deprecated!
As of Feb 11th 2020, [`request`](https://github.com/request/request) is fully deprecated. No new changes are expected to land. In fact, none have landed for some time. This package is also deprecated because it depends on `request`.
Fyi, here is the [reasoning of `request`'s deprecation](https://github.com/request/request/issues/3142) and a [list of alternative libraries](https://github.com/request/request/issues/3143).
---
The simplified HTTP request client 'request' with Promise support. Powered by Bluebird.
[Request](https://github.com/request/request) and [Bluebird](https://github.com/petkaantonov/bluebird) are pretty awesome, but I found myself using the same design pattern. Request-Promise adds a Bluebird-powered `.then(...)` method to Request call objects. By default, http response codes other than 2xx will cause the promise to be rejected. This can be overwritten by setting `options.simple = false`.
Also check out the new libraries that are **very similar to `request-promise` v4**:
- [`request-promise-native`](https://github.com/request/request-promise-native) v1 &ndash; Does not depend on Bluebird and uses native ES6 promises instead.
- [`request-promise-any`](https://github.com/request/request-promise-any) v1 &ndash; Allows you to register any Promise library supported by [`any-promise`](https://www.npmjs.com/package/any-promise).
---
## Migration from v3 to v4
1. `request` became a peer dependency. Thus make sure that `request` is installed into your project as a direct dependency. (`npm install --save request`)
2. Continuation Local Storage is no longer supported. However, you [can get back the support](https://github.com/request/request-promise/wiki/Getting-Back-Support-for-Continuation-Local-Storage) by using `request-promise-any`.
3. When you migrated your `transform` function to v3 and had to add `if (!(/^2/.test('' + response.statusCode))) { return resolveWithFullResponse ? response : body; }` you may now set the option `transform2xxOnly = true` instead.
## Migration from v2 to v3
1. The handling of the `transform` function got overhauled. This has two effects:
- `StatusCodeError.response` is the transformed instead of the original response now. This error is thrown for non-2xx responses when `options.simple` is `true` (default). Please update your `transform` functions to also cover the transformation of non-2xx responses. To get the old behavior you may add `if (!(/^2/.test('' + response.statusCode))) { return resolveWithFullResponse ? response : body; }` to the first line of your `transform` functions that are used for requests with `options.simple === true`. However, you may prefer updating your `transform` functions to being able to transform 2xx as well as non-2xx responses because this decouples their implementation from the use of the `simple` option when doing requests.
- If a transform operation throws an error, the request will be rejected with a `TransformError`. Its `cause` attribute contains the error thrown by the transform operation. Previously, the request was rejected directly with the error thrown by the transform operation. Wrapping it into a `TransformError` makes the error handling easier.
2. Bluebird got updated from v2 to v3. This won't make a difference for most use cases. However, if you use advanced Promise chains starting with the Promise returned by Request-Promise, please check [Bluebird's new features and changes](http://bluebirdjs.com/docs/new-in-bluebird-3.html).
---
## Installation
This module is installed via npm:
```
npm install --save request
npm install --save request-promise
```
`request` is defined as a peer-dependency and thus has to be installed separately.
## Cheat Sheet
``` js
var rp = require('request-promise');
```
Since `request-promise` wraps around `request` everything that works with `request` also works with `request-promise`. Also check out the [`request` docs](https://github.com/request/request) for more examples.
### Crawl a webpage
``` js
rp('http://www.google.com')
.then(function (htmlString) {
// Process html...
})
.catch(function (err) {
// Crawling failed...
});
```
### Crawl a webpage better
``` js
var cheerio = require('cheerio'); // Basically jQuery for node.js
var options = {
uri: 'http://www.google.com',
transform: function (body) {
return cheerio.load(body);
}
};
rp(options)
.then(function ($) {
// Process html like you would with jQuery...
})
.catch(function (err) {
// Crawling failed or Cheerio choked...
});
```
### GET something from a JSON REST API
``` js
var options = {
uri: 'https://api.github.com/user/repos',
qs: {
access_token: 'xxxxx xxxxx' // -> uri + '?access_token=xxxxx%20xxxxx'
},
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
};
rp(options)
.then(function (repos) {
console.log('User has %d repos', repos.length);
})
.catch(function (err) {
// API call failed...
});
```
### POST data to a JSON REST API
Set `option.body` to your data and `json: true` to encode the body as JSON. See below for HTML forms.
``` js
var options = {
method: 'POST',
uri: 'http://api.posttestserver.com/post',
body: {
some: 'payload'
},
json: true // Automatically stringifies the body to JSON
};
rp(options)
.then(function (parsedBody) {
// POST succeeded...
})
.catch(function (err) {
// POST failed...
});
```
### POST like HTML forms do
Pass your data to `options.form` to encode the body the same way as HTML forms do:
``` js
var options = {
method: 'POST',
uri: 'http://posttestserver.com/post.php',
form: {
// Like <input type="text" name="name">
name: 'Josh'
},
headers: {
/* 'content-type': 'application/x-www-form-urlencoded' */ // Is set automatically
}
};
rp(options)
.then(function (body) {
// POST succeeded...
})
.catch(function (err) {
// POST failed...
});
```
If you want to include a file upload then use `options.formData`:
``` js
var options = {
method: 'POST',
uri: 'http://posttestserver.com/post.php',
formData: {
// Like <input type="text" name="name">
name: 'Jenn',
// Like <input type="file" name="file">
file: {
value: fs.createReadStream('test/test.jpg'),
options: {
filename: 'test.jpg',
contentType: 'image/jpg'
}
}
},
headers: {
/* 'content-type': 'multipart/form-data' */ // Is set automatically
}
};
rp(options)
.then(function (body) {
// POST succeeded...
})
.catch(function (err) {
// POST failed...
});
```
### Include a cookie
``` js
var tough = require('tough-cookie');
// Easy creation of the cookie - see tough-cookie docs for details
let cookie = new tough.Cookie({
key: "some_key",
value: "some_value",
domain: 'api.mydomain.com',
httpOnly: true,
maxAge: 31536000
});
// Put cookie in an jar which can be used across multiple requests
var cookiejar = rp.jar();
cookiejar.setCookie(cookie, 'https://api.mydomain.com');
// ...all requests to https://api.mydomain.com will include the cookie
var options = {
uri: 'https://api.mydomain.com/...',
jar: cookiejar // Tells rp to include cookies in jar that match uri
};
rp(options)
.then(function (body) {
// Request succeeded...
})
.catch(function (err) {
// Request failed...
});
```
### Get the full response instead of just the body
``` js
var options = {
method: 'DELETE',
uri: 'http://my-server/path/to/resource/1234',
resolveWithFullResponse: true // <--- <--- <--- <---
};
rp(options)
.then(function (response) {
console.log("DELETE succeeded with status %d", response.statusCode);
})
.catch(function (err) {
// Delete failed...
});
```
### Get a rejection only if the request failed for technical reasons
``` js
var options = {
uri: 'http://www.google.com/this-page-does-not-exist.html',
simple: false // <--- <--- <--- <---
};
rp(options)
.then(function (body) {
// Request succeeded but might as well be a 404
// Usually combined with resolveWithFullResponse = true to check response.statusCode
})
.catch(function (err) {
// Request failed due to technical reasons...
});
```
---
**For more options checkout the [Request docs](https://github.com/request/request#requestoptions-callback).**
---
## API in Detail
Consider Request-Promise being:
- A Request object
- With an [identical API](https://github.com/request/request): `require('request-promise') == require('request')` so to say
- However, **STREAMING THE RESPONSE** (e.g. `.pipe(...)`) is **DISCOURAGED** because Request-Promise would grow the memory footprint for large requests unnecessarily high. Use the original Request library for that. You can use both libraries in the same project.
- Plus some methods on a request call object:
- `rp(...).then(...)` or e.g. `rp.post(...).then(...)` which turn `rp(...)` and `rp.post(...)` into promises
- `rp(...).catch(...)` or e.g. `rp.del(...).catch(...)` which is the same method as provided by Bluebird promises
- Errors that the `request` library would pass to the callback are wrapped by `request-promise` and then passed to the catch handler. See [code example](https://github.com/request/request-promise#thenonfulfilled-onrejected) below.
- `rp(...).finally(...)` or e.g. `rp.put(...).finally(...)` which is the same method as provided by Bluebird promises
- `rp(...).cancel()` or e.g. `rp.get(...).cancel()` which cancels the request
- `rp(...).promise()` or e.g. `rp.head(...).promise()` which returns the underlying promise so you can access the full [Bluebird API](https://github.com/petkaantonov/bluebird/blob/master/API.md)
- Plus some additional options:
- `simple = true` which is a boolean to set whether status codes other than 2xx should also reject the promise
- `resolveWithFullResponse = false` which is a boolean to set whether the promise should be resolved with the full response or just the response body
- `transform` which takes a function to transform the response into a custom value with which the promise is resolved
- `transform2xxOnly = false` which is a boolean to set whether the transform function is applied to all responses or only to those with a 2xx status code
The objects returned by request calls like `rp(...)` or e.g. `rp.post(...)` are regular Promises/A+ compliant promises and can be assimilated by any compatible promise library.
The methods `.then(...)`, `.catch(...)`, and `.finally(...)` - which you can call on the request call objects - return a full-fledged Bluebird promise. That means you have the full [Bluebird API](https://github.com/petkaantonov/bluebird/blob/master/API.md) available for further chaining. E.g.: `rp(...).then(...).spread(...)` If, however, you need a method other than `.then(...)`, `.catch(...)`, or `.finally(...)` to be **FIRST** in the chain, use `.promise()`: `rp(...).promise().bind(...).then(...)`
### .then(onFulfilled, onRejected)
``` js
// As a Request user you would write:
var request = require('request');
request('http://google.com', function (err, response, body) {
if (err) {
handleError({ error: err, response: response, ... });
} else if (!(/^2/.test('' + response.statusCode))) { // Status Codes other than 2xx
handleError({ error: body, response: response, ... });
} else {
process(body);
}
});
// As a Request-Promise user you can now write the equivalent code:
var rp = require('request-promise');
rp('http://google.com')
.then(process, handleError);
```
``` js
// The same is available for all http method shortcuts:
request.post('http://example.com/api', function (err, response, body) { ... });
rp.post('http://example.com/api').then(...);
```
### .catch(onRejected)
``` js
rp('http://google.com')
.catch(handleError);
// ... is syntactical sugar for:
rp('http://google.com')
.then(null, handleError);
// However, this:
rp('http://google.com')
.then(process)
.catch(handleError);
// ... is safer than:
rp('http://google.com')
.then(process, handleError);
```
For more info on `.then(process).catch(handleError)` versus `.then(process, handleError)`, see Bluebird docs on [promise anti-patterns](http://bluebirdjs.com/docs/anti-patterns.html#the-.then).
### .finally(onFinished)
``` js
rp('http://google.com')
.finally(function () {
// This is called after the request finishes either successful or not successful.
});
```
### .cancel()
This method cancels the request using [Bluebird's cancellation feature](http://bluebirdjs.com/docs/api/cancellation.html).
When `.cancel()` is called:
- the promise will neither be resolved nor rejected and
- the request is [aborted](https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_request_abort).
### .promise() - For advanced use cases
In order to not pollute the Request call objects with the methods of the underlying Bluebird promise, only `.then(...)`, `.catch(...)`, and `.finally(...)` were exposed to cover most use cases. The effect is that any methods of a Bluebird promise other than `.then(...)`, `.catch(...)`, or `.finally(...)` cannot be used as the **FIRST** method in the promise chain:
``` js
// This works:
rp('http://google.com').then(function () { ... });
rp('http://google.com').catch(function () { ... });
// This works as well since additional methods are only used AFTER the FIRST call in the chain:
rp('http://google.com').then(function () { ... }).spread(function () { ... });
rp('http://google.com').catch(function () { ... }).error(function () { ... });
// Using additional methods as the FIRST call in the chain does not work:
// rp('http://google.com').bind(this).then(function () { ... });
// Use .promise() in these cases:
rp('http://google.com').promise().bind(this).then(function () { ... });
```
### Fulfilled promises and the `resolveWithFullResponse` option
``` js
// Per default the body is passed to the fulfillment handler:
rp('http://google.com')
.then(function (body) {
// Process the html of the Google web page...
});
// The resolveWithFullResponse options allows to pass the full response:
rp({ uri: 'http://google.com', resolveWithFullResponse: true })
.then(function (response) {
// Access response.statusCode, response.body etc.
});
```
### Rejected promises and the `simple` option
``` js
// The rejection handler is called with a reason object...
rp('http://google.com')
.catch(function (reason) {
// Handle failed request...
});
// ... and would be equivalent to this Request-only implementation:
var options = { uri: 'http://google.com' };
request(options, function (err, response, body) {
var reason;
if (err) {
reason = {
cause: err,
error: err,
options: options,
response: response
};
} else if (!(/^2/.test('' + response.statusCode))) { // Status Codes other than 2xx
reason = {
statusCode: response.statusCode,
error: body,
options: options,
response: response
};
}
if (reason) {
// Handle failed request...
}
});
// If you pass the simple option as false...
rp({ uri: 'http://google.com', simple: false })
.catch(function (reason) {
// Handle failed request...
});
// ... the equivalent Request-only code would be:
request(options, function (err, response, body) {
if (err) {
var reason = {
cause: err,
error: err,
options: options,
response: response
};
// Handle failed request...
}
});
// E.g. a 404 would now fulfill the promise.
// Combine it with resolveWithFullResponse = true to check the status code in the fulfillment handler.
```
With version 0.4 the reason objects became Error objects with identical properties to ensure backwards compatibility. These new Error types allow targeted catch blocks:
``` js
var errors = require('request-promise/errors');
rp('http://google.com')
.catch(errors.StatusCodeError, function (reason) {
// The server responded with a status codes other than 2xx.
// Check reason.statusCode
})
.catch(errors.RequestError, function (reason) {
// The request failed due to technical reasons.
// reason.cause is the Error object Request would pass into a callback.
});
```
### The `transform` function
You can pass a function to `options.transform` to generate a custom fulfillment value when the promise gets resolved.
``` js
// Just for fun you could reverse the response body:
var options = {
uri: 'http://google.com',
transform: function (body, response, resolveWithFullResponse) {
return body.split('').reverse().join('');
}
};
rp(options)
.then(function (reversedBody) {
// ;D
});
// However, you could also do something useful:
var $ = require('cheerio'); // Basically jQuery for node.js
function autoParse(body, response, resolveWithFullResponse) {
// FIXME: The content type string could contain additional values like the charset.
// Consider using the `content-type` library for a robust comparison.
if (response.headers['content-type'] === 'application/json') {
return JSON.parse(body);
} else if (response.headers['content-type'] === 'text/html') {
return $.load(body);
} else {
return body;
}
}
options.transform = autoParse;
rp(options)
.then(function (autoParsedBody) {
// :)
});
// You can go one step further and set the transform as the default:
var rpap = rp.defaults({ transform: autoParse });
rpap('http://google.com')
.then(function (autoParsedBody) {
// :)
});
rpap('http://echojs.com')
.then(function (autoParsedBody) {
// =)
});
```
The third `resolveWithFullResponse` parameter of the transform function is equivalent to the option passed with the request. This allows to distinguish whether just the transformed body or the whole response shall be returned by the transform function:
``` js
function reverseBody(body, response, resolveWithFullResponse) {
response.body = response.body.split('').reverse().join('');
return resolveWithFullResponse ? response : response.body;
}
```
As of Request-Promise v3 the transform function is ALWAYS executed for non-2xx responses. When `options.simple` is set to `true` (default) then non-2xx responses are rejected with a `StatusCodeError`. In this case the error contains the transformed response:
``` js
var options = {
uri: 'http://the-server.com/will-return/404',
simple: true,
transform: function (body, response, resolveWithFullResponse) { /* ... */ }
};
rp(options)
.catch(errors.StatusCodeError, function (reason) {
// reason.response is the transformed response
});
```
You may set `options.transform2xxOnly = true` to only execute the transform function for responses with a 2xx status code. For other status codes &ndash; independent of any other settings, e.g. `options.simple` &ndash; the transform function is not executed.
#### Error handling
If the transform operation fails (throws an error) the request will be rejected with a `TransformError`:
``` js
var errors = require('request-promise/errors');
var options = {
uri: 'http://google.com',
transform: function (body, response, resolveWithFullResponse) {
throw new Error('Transform failed!');
}
};
rp(options)
.catch(errors.TransformError, function (reason) {
console.log(reason.cause.message); // => Transform failed!
// reason.response is the original response for which the transform operation failed
});
```
## Experimental Support for Continuation Local Storage
Continuation Local Storage is no longer supported. However, you [can get back the support](https://github.com/request/request-promise/wiki/Getting-Back-Support-for-Continuation-Local-Storage) by using `request-promise-any`.
## Debugging
The ways to debug the operation of Request-Promise are the same [as described](https://github.com/request/request#debugging) for Request. These are:
1. Launch the node process like `NODE_DEBUG=request node script.js` (`lib,request,otherlib` works too).
2. Set `require('request-promise').debug = true` at any time (this does the same thing as #1).
3. Use the [request-debug module](https://github.com/nylen/request-debug) to view request and response headers and bodies. Instrument Request-Promise with `require('request-debug')(rp);`.
## Mocking Request-Promise
Usually you want to mock the whole request function which is returned by `require('request-promise')`. This is not possible by using a mocking library like [sinon.js](http://sinonjs.org) alone. What you need is a library that ties into the module loader and makes sure that your mock is returned whenever the tested code is calling `require('request-promise')`. [Mockery](https://github.com/mfncooper/mockery) is one of such libraries.
@florianschmidt1994 kindly shared his solution:
```javascript
before(function (done) {
var filename = "fileForResponse";
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
useCleanCache: true
});
mockery.registerMock('request-promise', function () {
var response = fs.readFileSync(__dirname + '/data/' + filename, 'utf8');
return Bluebird.resolve(response.trim());
});
done();
});
after(function (done) {
mockery.disable();
mockery.deregisterAll();
done();
});
describe('custom test case', function () {
// Test some function/module/... which uses request-promise
// and it will always receive the predefined "fileForResponse" as data, e.g.:
var rp = require('request-promise');
rp(...).then(function(data) {
// ➞ data is what is in fileForResponse
});
});
```
Based on that you may now build a more sophisticated mock. [Sinon.js](http://sinonjs.org) may be of help as well.
## Contributing
To set up your development environment:
1. clone the repo to your desktop,
2. in the shell `cd` to the main folder,
3. hit `npm install`,
4. hit `npm install gulp -g` if you haven't installed gulp globally yet, and
5. run `gulp dev`. (Or run `node ./node_modules/.bin/gulp dev` if you don't want to install gulp globally.)
`gulp dev` watches all source files and if you save some changes it will lint the code and execute all tests. The test coverage report can be viewed from `./coverage/lcov-report/index.html`.
If you want to debug a test you should use `gulp test-without-coverage` to run all tests without obscuring the code by the test coverage instrumentation.
## Change History
- v4.2.5 (2019-11-03)
- Security fix: bumped `request-promise-core` which bumps `lodash` to `^4.17.15`. See [vulnerabilty reports](https://snyk.io/vuln/search?q=lodash&type=npm).
*(Thanks to @rishabh-chowdhary for reporting this in pull request [#326](https://github.com/request/request-promise/pull/326).)*
- v4.2.4 (2019-02-14)
- Corrected mistakenly set `tough-cookie` version, now `^2.3.3`
*(Thanks to @evocateur for pointing this out.)*
- If you installed `request-promise@4.2.3` please make sure after the upgrade that `request` and `request-promise` use the same physical copy of `tough-cookie`.
- v4.2.3 (2019-02-14)
- Using stricter `tough-cookie@~2.3.3` to avoid installing `tough-cookie@3` which introduces breaking changes
*(Thanks to @aomdoa for pull request [#299](https://github.com/request/request-promise/pull/299))*
- Security fix: bumped `lodash` to `^4.17.11`, see [vulnerabilty reports](https://snyk.io/vuln/search?q=lodash&type=npm)
- v4.2.2 (2017-09-22)
- Upgraded `tough-cookie` to a version without regex DoS vulnerability
*(Thanks to @rouanw for [pull request #226](https://github.com/request/request-promise/pull/226))*
- v4.2.1 (2017-05-07)
- Fix that allows to use `tough-cookie` for cookie creation
*(Thanks to @ScottyMJacobson for reporting [issue #183](https://github.com/request/request-promise/issues/183))*
- Added [cookie handling example](https://github.com/request/request-promise#include-a-cookie) to the cheat sheet
*(Thanks to @chovy and @ProfessorTom for [asking for it](https://github.com/request/request-promise/issues/79))*
- v4.2.0 (2017-03-16)
- Updated `bluebird` to v3.5
*(Thanks to @acinader for [pull request #181](https://github.com/request/request-promise/pull/181))*
- v4.1.1 (2016-08-08)
- Renamed internally used package `@request/promise-core` to `request-promise-core` because there where [too](https://github.com/request/request-promise/issues/137) [many](https://github.com/request/request-promise/issues/141) issues with the scoped package name
*(Thanks to @cabrinoob, @crazy4groovy, @dsandor, @KpjComp, @lorenwest, @Reisyukaku, @tehChromic, @todd for providing helpful information.)*
- v4.1.0 (2016-07-30)
- Added cancellation support
*(Thanks to @not-an-aardvark for [pull request #123](https://github.com/request/request-promise/pull/123))*
- v4.0.2 (2016-07-18)
- Fix for using with module bundlers like Webpack and Browserify
- v4.0.1 (2016-07-17)
- Fixed `@request/promise-core` version for safer versioning
- v4.0.0 (2016-07-15)
- **Breaking Change**: `request` is declared as a peer dependency which has to be installed separately by the user now
- **Breaking Change**: Dropped support for Continuation Local Storage since [`request-promise-any`](https://github.com/request/request-promise-any) can be [used](https://github.com/request/request-promise/wiki/Getting-Back-Support-for-Continuation-Local-Storage) for that now
- Introduced the `transform2xxOnly` option to ease the breaking change regarding the new `transform` handling in v3.0.0
*(Thanks to @stevage for pointing out the effect of the breaking change in [issue #131](https://github.com/request/request-promise/issues/131))*
- Resolved issues [#65](https://github.com/request/request-promise/issues/65) and [#71](https://github.com/request/request-promise/issues/71) by publishing nearly identical libraries to support other Promise implementations: [`request-promise-native`](https://github.com/request/request-promise-native) and [`request-promise-any`](https://github.com/request/request-promise-any)
*(Thanks to @benjamingr, @eilgin, @gillesdemey, @hildjj, @iggycoloma, @jonathanong, @knpwrs, @MarkHerhold, @massimocode, @mikeal, @niftylettuce, @raitucarp, @sherdeadlock, @tonylukasavage, and @vgoloviznin for the valuable discussions!)*
- Relicensed this library with the ISC license
- v3.0.0 (2016-04-16)
- **Breaking Change**: Overhauled the handling of the `transform` function
*(Thanks to @Limess for explaining the need in [issue #86](https://github.com/request/request-promise/issues/86))*
- **Breaking Change**: Updated `bluebird` to v3
*(Thanks to @BrandonSmith for [pull request #103](https://github.com/request/request-promise/pull/103))*
- Improved `StatusCodeError.message`
- Updated `lodash` to v4.6
- Improved README in regard to `.catch(...)` best practice
*(Thanks to @RebootJeff for [pull request #98](https://github.com/request/request-promise/pull/98))*
- v2.0.1 (2016-02-17)
- Updated `lodash` to v4
*(Thanks to @ratson for [pull request #94](https://github.com/request/request-promise/pull/94))*
- v2.0.0 (2016-01-12)
- **Breaking Change**: Removed explicit `cls-bluebird` dependency which has to be installed by the user now
*(Thanks to @hildjj for his [pull request #75](https://github.com/request/request-promise/pull/75))*
- `npm shrinkwrap` now works for `npm@3` users who don't use `continuation-local-storage`
*(Thanks to @toboid and @rstacruz for reporting the issue in [issue #70](https://github.com/request/request-promise/issues/70) and [issue #82](https://github.com/request/request-promise/issues/82))*
- v1.0.2 (2015-10-22)
- Removed `continuation-local-storage` from peer dependencies as it was unnecessary
*(Thanks to @mrhyde for working on a better solution discussed in [issue #70](https://github.com/request/request-promise/issues/70))*
- v1.0.1 (2015-10-14)
- Fixed a npm warning by marking `continuation-local-storage` as a peer dependency
- v1.0.0 (2015-10-11)
- **Breaking Change**: Some errors that were previously thrown synchronously - e.g. for wrong input parameters - are now passed to the rejected promise instead
*(Thanks to @josnidhin for suggesting that in [issue #43](https://github.com/request/request-promise/issues/43))*
- **Breaking Change**: Request-Promise does not load its own Bluebird prototype anymore. If you use Bluebird in your project and altered the prototype then Request-Promise may use your altered Bluebird prototype internally.
- For HEAD requests the headers instead of an empty body is returned (unless `resolveWithFullResponse = true` is used)
*(Thanks to @zcei for proposing the change in [issue #58](https://github.com/request/request-promise/issues/58))*
- Extended `transform` function by a third `resolveWithFullResponse` parameter
- Added experimental support for continuation local storage
*(Thanks to @silverbp preparing this in [issue #64](https://github.com/request/request-promise/issues/64))*
- Added node.js 4 to the Travis CI build
- Updated the README
*(Thanks to many people for their feedback in issues [#55](https://github.com/request/request-promise/issues/55) and [#59](https://github.com/request/request-promise/issues/59))*
- v0.4.3 (2015-07-27)
- Reduced overhead by just requiring used lodash functions instead of the whole lodash library
*(Thanks to @luanmuniz for [pull request #54](https://github.com/request/request-promise/pull/54))*
- Updated dependencies
- v0.4.2 (2015-04-12)
- Updated dependencies
- v0.4.1 (2015-03-20)
- Improved Error types to work in browsers without v8 engine
*(Thanks to @nodiis for [pull request #40](https://github.com/request/request-promise/pull/40))*
- v0.4.0 (2015-02-08)
- Introduced Error types used for the reject reasons (See last part [this section](#rejected-promises-and-the-simple-option))
*(Thanks to @jakecraige for starting the discussion in [issue #38](https://github.com/request/request-promise/issues/38))*
- **Minor Breaking Change:** The reject reason objects became actual Error objects. However, `typeof reason === 'object'` still holds true and the error objects have the same properties as the previous reason objects. If the reject handler only accesses the properties on the reason object - which is usually the case - no migration is required.
- Added io.js and node.js 0.12 to the Travis CI build
- v0.3.3 (2015-01-19)
- Fixed handling possibly unhandled rejections to work with the latest version of Bluebird
*(Thanks to @slang800 for reporting this in [issue #36](https://github.com/request/request-promise/issues/36))*
- v0.3.2 (2014-11-17)
- Exposed `.finally(...)` to allow using it as the first method in the promise chain
*(Thanks to @hjpbarcelos for his [pull request #28](https://github.com/request/request-promise/pull/28))*
- v0.3.1 (2014-11-11)
- Added the `.promise()` method for advanced Bluebird API usage
*(Thanks to @devo-tox for his feedback in [issue #27](https://github.com/request/request-promise/issues/27))*
- v0.3.0 (2014-11-10)
- Carefully rewritten from scratch to make Request-Promise a drop-in replacement for Request
## License (ISC)
In case you never heard about the [ISC license](http://en.wikipedia.org/wiki/ISC_license) it is functionally equivalent to the MIT license.
See the [LICENSE file](LICENSE) for details.
+3
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = require('request-promise-core/errors');
+52
View File
@@ -0,0 +1,52 @@
'use strict';
var Bluebird = require('bluebird').getNewLibraryCopy(),
configure = require('request-promise-core/configure/request2'),
stealthyRequire = require('stealthy-require');
try {
// Load Request freshly - so that users can require an unaltered request instance!
var request = stealthyRequire(require.cache, function () {
return require('@cypress/request');
},
function () {
require('tough-cookie');
}, module);
} catch (err) {
/* istanbul ignore next */
var EOL = require('os').EOL;
/* istanbul ignore next */
console.error(EOL + '###' + EOL + '### The "request" library is not installed automatically anymore.' + EOL + '### But is a dependency of "request-promise".' + EOL + '### Please install it with:' + EOL + '### npm install request --save' + EOL + '###' + EOL);
/* istanbul ignore next */
throw err;
}
Bluebird.config({cancellation: true});
configure({
request: request,
PromiseImpl: Bluebird,
expose: [
'then',
'catch',
'finally',
'cancel',
'promise'
// Would you like to expose more Bluebird methods? Try e.g. `rp(...).promise().tap(...)` first. `.promise()` returns the full-fledged Bluebird promise.
],
constructorMixin: function (resolve, reject, onCancel) {
var self = this;
onCancel(function () {
self.abort();
});
}
});
request.bindCLS = function RP$bindCLS() {
throw new Error('CLS support was dropped. To get it back read: https://github.com/request/request-promise/wiki/Getting-Back-Support-for-Continuation-Local-Storage');
};
module.exports = request;
@@ -0,0 +1,12 @@
Copyright (c) 2015, Salesforce.com, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,596 @@
# tough-cookie
[RFC 6265](https://tools.ietf.org/html/rfc6265) Cookies and CookieJar for Node.js
[![npm package](https://nodei.co/npm/tough-cookie.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/tough-cookie/)
[![Build Status](https://travis-ci.org/salesforce/tough-cookie.svg?branch=master)](https://travis-ci.org/salesforce/tough-cookie)
## Synopsis
```javascript
var tough = require("tough-cookie");
var Cookie = tough.Cookie;
var cookie = Cookie.parse(header);
cookie.value = "somethingdifferent";
header = cookie.toString();
var cookiejar = new tough.CookieJar();
// Asynchronous!
var cookie = await cookiejar.setCookie(
cookie,
"https://currentdomain.example.com/path"
);
var cookies = await cookiejar.getCookies("https://example.com/otherpath");
// Or with callbacks!
cookiejar.setCookie(
cookie,
"https://currentdomain.example.com/path",
function (err, cookie) {
/* ... */
}
);
cookiejar.getCookies("http://example.com/otherpath", function (err, cookies) {
/* ... */
});
```
Why the name? NPM modules `cookie`, `cookies` and `cookiejar` were already taken.
## Installation
It's _so_ easy! Install with `npm` or your preferred package manager.
```sh
npm install tough-cookie
```
## Node.js Version Support
We follow the [node.js release schedule](https://github.com/nodejs/Release#release-schedule) and support all versions that are in Active LTS or Maintenance. We will always do a major release when dropping support for older versions of node, and we will do so in consultation with our community.
## API
### tough
The top-level exports from `require('tough-cookie')` can all be used as pure functions and don't need to be bound.
#### `parseDate(string)`
Parse a cookie date string into a `Date`. Parses according to [RFC 6265 Section 5.1.1](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1), not `Date.parse()`.
#### `formatDate(date)`
Format a `Date` into an [RFC 822](https://datatracker.ietf.org/doc/html/rfc822#section-5) string (the RFC 6265 recommended format).
#### `canonicalDomain(str)`
Transforms a domain name into a canonical domain name. The canonical domain name is a domain name that has been trimmed, lowercased, stripped of leading dot, and optionally punycode-encoded ([Section 5.1.2 of RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.2)). For the most part, this function is idempotent (calling the function with the output from a previous call returns the same output).
#### `domainMatch(str, domStr[, canonicalize=true])`
Answers "does this real domain match the domain in a cookie?". The `str` is the "current" domain name and the `domStr` is the "cookie" domain name. Matches according to [RFC 6265 Section 5.1.3](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3), but it helps to think of it as a "suffix match".
The `canonicalize` parameter toggles whether the domain parameters get normalized with `canonicalDomain` or not.
#### `defaultPath(path)`
Given a current request/response path, gives the path appropriate for storing in a cookie. This is basically the "directory" of a "file" in the path, but is specified by [Section 5.1.4 of the RFC](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4).
The `path` parameter MUST be _only_ the pathname part of a URI (excluding the hostname, query, fragment, and so on). This is the `.pathname` property of node's `uri.parse()` output.
#### `pathMatch(reqPath, cookiePath)`
Answers "does the request-path path-match a given cookie-path?" as per [RFC 6265 Section 5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). Returns a boolean.
This is essentially a prefix-match where `cookiePath` is a prefix of `reqPath`.
#### `parse(cookieString[, options])`
Alias for [`Cookie.parse(cookieString[, options])`](#cookieparsecookiestring-options).
#### `fromJSON(string)`
Alias for [`Cookie.fromJSON(string)`](#cookiefromjsonstrorobj).
#### `getPublicSuffix(hostname)`
Returns the public suffix of this hostname. The public suffix is the shortest domain name upon which a cookie can be set. Returns `null` if the hostname cannot have cookies set for it.
For example: `www.example.com` and `www.subdomain.example.com` both have public suffix `example.com`.
For further information, see the [Public Suffix List](http://publicsuffix.org/). This module derives its list from that site. This call is a wrapper around [`psl`](https://www.npmjs.com/package/psl)'s [`get` method](https://www.npmjs.com/package/psl##pslgetdomain).
#### `cookieCompare(a, b)`
For use with `.sort()`, sorts a list of cookies into the recommended order given in step 2 of ([RFC 6265 Section 5.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.4)). The sort algorithm is, in order of precedence:
- Longest `.path`
- oldest `.creation` (which has a 1-ms precision, same as `Date`)
- lowest `.creationIndex` (to get beyond the 1-ms precision)
```javascript
var cookies = [
/* unsorted array of Cookie objects */
];
cookies = cookies.sort(cookieCompare);
```
> **Note**: Since the JavaScript `Date` is limited to a 1-ms precision, cookies within the same millisecond are entirely possible. This is especially true when using the `now` option to `.setCookie()`. The `.creationIndex` property is a per-process global counter, assigned during construction with `new Cookie()`, which preserves the spirit of the RFC sorting: older cookies go first. This works great for `MemoryCookieStore` since `Set-Cookie` headers are parsed in order, but is not so great for distributed systems. Sophisticated `Store`s may wish to set this to some other _logical clock_ so that if cookies A and B are created in the same millisecond, but cookie A is created before cookie B, then `A.creationIndex < B.creationIndex`. If you want to alter the global counter, which you probably _shouldn't_ do, it's stored in `Cookie.cookiesCreated`.
#### `permuteDomain(domain)`
Generates a list of all possible domains that `domainMatch()` the parameter. Can be handy for implementing cookie stores.
#### `permutePath(path)`
Generates a list of all possible paths that `pathMatch()` the parameter. Can be handy for implementing cookie stores.
### Cookie
Exported via `tough.Cookie`.
#### `Cookie.parse(cookieString[, options])`
Parses a single Cookie or Set-Cookie HTTP header into a `Cookie` object. Returns `undefined` if the string can't be parsed.
The options parameter is not required and currently has only one property:
- _loose_ - boolean - if `true` enable parsing of keyless cookies like `=abc` and `=`, which are not RFC-compliant.
If options is not an object it is ignored, which means it can be used with [`Array#map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
To process the Set-Cookie header(s) on a node HTTP/HTTPS response:
```javascript
if (Array.isArray(res.headers["set-cookie"]))
cookies = res.headers["set-cookie"].map(Cookie.parse);
else cookies = [Cookie.parse(res.headers["set-cookie"])];
```
_Note:_ In version 2.3.3, tough-cookie limited the number of spaces before the `=` to 256 characters. This limitation was removed in version 2.3.4.
For more details, see [issue #92](https://github.com/salesforce/tough-cookie/issues/92).
#### Properties
Cookie object properties:
- _key_ - string - the name or key of the cookie (default `""`)
- _value_ - string - the value of the cookie (default `""`)
- _expires_ - `Date` - if set, the `Expires=` attribute of the cookie (defaults to the string `"Infinity"`). See `setExpires()`
- _maxAge_ - seconds - if set, the `Max-Age=` attribute _in seconds_ of the cookie. Can also be set to strings `"Infinity"` and `"-Infinity"` for non-expiry and immediate-expiry, respectively. See `setMaxAge()`
- _domain_ - string - the `Domain=` attribute of the cookie
- _path_ - string - the `Path=` of the cookie
- _secure_ - boolean - the `Secure` cookie flag
- _httpOnly_ - boolean - the `HttpOnly` cookie flag
- _sameSite_ - string - the `SameSite` cookie attribute (from [RFC 6265bis](#rfc-6265bis)); must be one of `none`, `lax`, or `strict`
- _extensions_ - `Array` - any unrecognized cookie attributes as strings (even if equal-signs inside)
- _creation_ - `Date` - when this cookie was constructed
- _creationIndex_ - number - set at construction, used to provide greater sort precision (see `cookieCompare(a,b)` for a full explanation)
After a cookie has been passed through `CookieJar.setCookie()` it has the following additional attributes:
- _hostOnly_ - boolean - is this a host-only cookie (that is, no Domain field was set, but was instead implied).
- _pathIsDefault_ - boolean - if true, there was no Path field on the cookie and `defaultPath()` was used to derive one.
- _creation_ - `Date` - **modified** from construction to when the cookie was added to the jar.
- _lastAccessed_ - `Date` - last time the cookie got accessed. Affects cookie cleaning after it is implemented. Using `cookiejar.getCookies(...)` updates this attribute.
#### `new Cookie([properties])`
Receives an options object that can contain any of the above Cookie properties. Uses the default for unspecified properties.
#### `.toString()`
Encodes to a Set-Cookie header value. The Expires cookie field is set using `formatDate()`, but is omitted entirely if `.expires` is `Infinity`.
#### `.cookieString()`
Encodes to a Cookie header value (specifically, the `.key` and `.value` properties joined with `"="`).
#### `.setExpires(string)`
Sets the expiry based on a date-string passed through `parseDate()`. If parseDate returns `null` (that is, can't parse this date string), `.expires` is set to `"Infinity"` (a string).
#### `.setMaxAge(number)`
Sets the maxAge in seconds. Coerces `-Infinity` to `"-Infinity"` and `Infinity` to `"Infinity"` so it correctly serializes to JSON.
#### `.expiryDate([now=Date.now()])`
`expiryTime()` computes the absolute unix-epoch milliseconds that this cookie expires. `expiryDate()` works similarly, except it returns a `Date` object. Note that in both cases the `now` parameter should be milliseconds.
Max-Age takes precedence over Expires (as per the RFC). The `.creation` attribute -- or, by default, the `now` parameter -- is used to offset the `.maxAge` attribute.
If Expires (`.expires`) is set, that's returned.
Otherwise, `expiryTime()` returns `Infinity` and `expiryDate()` returns a `Date` object for "Tue, 19 Jan 2038 03:14:07 GMT" (latest date that can be expressed by a 32-bit `time_t`; the common limit for most user-agents).
#### `.TTL([now=Date.now()])`
Computes the TTL relative to `now` (milliseconds). The same precedence rules as for `expiryTime`/`expiryDate` apply.
`Infinity` is returned for cookies without an explicit expiry and `0` is returned if the cookie is expired. Otherwise a time-to-live in milliseconds is returned.
#### `.canonicalizedDomain()`
#### `.cdomain()`
Returns the canonicalized `.domain` field. This is lower-cased and punycode ([RFC 3490](https://datatracker.ietf.org/doc/html/rfc3490)) encoded if the domain has any non-ASCII characters.
#### `.toJSON()`
For convenience in using `JSON.serialize(cookie)`. Returns a plain-old `Object` that can be JSON-serialized.
Any `Date` properties (such as `.expires`, `.creation`, and `.lastAccessed`) are exported in ISO format (`.toISOString()`).
> **NOTE**: Custom `Cookie` properties are discarded. In tough-cookie 1.x, since there was no `.toJSON` method explicitly defined, all enumerable properties were captured. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array.
#### `Cookie.fromJSON(strOrObj)`
Does the reverse of `cookie.toJSON()`. If passed a string, will `JSON.parse()` that first.
Any `Date` properties (such as `.expires`, `.creation`, and `.lastAccessed`) are parsed via [`Date.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse), not tough-cookie's `parseDate`, since ISO timestamps are being handled at this layer.
Returns `null` upon a JSON parsing error.
#### `.clone()`
Does a deep clone of this cookie, implemented exactly as `Cookie.fromJSON(cookie.toJSON())`.
#### `.validate()`
Status: _IN PROGRESS_. Works for a few things, but is by no means comprehensive.
Validates cookie attributes for semantic correctness. Useful for "lint" checking any Set-Cookie headers you generate. For now, it returns a boolean, but eventually could return a reason string. Future-proof with this construct:
```javascript
if (cookie.validate() === true) {
// it's tasty
} else {
// yuck!
}
```
### CookieJar
Exported via `tough.CookieJar`.
#### `CookieJar([store][, options])`
Simply use `new CookieJar()`. If a custom store is not passed to the constructor, a [`MemoryCookieStore`](#memorycookiestore) is created and used.
The `options` object can be omitted and can have the following properties:
- _rejectPublicSuffixes_ - boolean - default `true` - reject cookies with domains like "com" and "co.uk"
- _looseMode_ - boolean - default `false` - accept malformed cookies like `bar` and `=bar`, which have an implied empty name.
- _prefixSecurity_ - string - default `silent` - set to `'unsafe-disabled'`, `'silent'`, or `'strict'`. See [Cookie Prefixes](#cookie-prefixes) below.
- _allowSpecialUseDomain_ - boolean - default `true` - accepts special-use domain suffixes, such as `local`. Useful for testing purposes.
This is not in the standard, but is used sometimes on the web and is accepted by most browsers.
#### `.setCookie(cookieOrString, currentUrl[, options][, callback(err, cookie)])`
Attempt to set the cookie in the cookie jar. The cookie has updated `.creation`, `.lastAccessed` and `.hostOnly` properties. And returns a promise if a callback is not provided.
The `options` object can be omitted and can have the following properties:
- _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects `HttpOnly` cookies.
- _secure_ - boolean - autodetect from URL - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` this defaults to `true`, otherwise `false`.
- _now_ - Date - default `new Date()` - what to use for the creation or access time of cookies.
- _ignoreError_ - boolean - default `false` - silently ignore things like parse errors and invalid domains. `Store` errors aren't ignored by this option.
- _sameSiteContext_ - string - default unset - set to `'none'`, `'lax'`, or `'strict'` See [SameSite Cookies](#samesite-cookies) below.
As per the RFC, the `.hostOnly` property is set if there was no "Domain=" parameter in the cookie string (or `.domain` was null on the Cookie object). The `.domain` property is set to the fully-qualified hostname of `currentUrl` in this case. Matching this cookie requires an exact hostname match (not a `domainMatch` as per usual).
#### `.setCookieSync(cookieOrString, currentUrl[, options])`
Synchronous version of [`setCookie`](#setcookiecookieorstring-currenturl-options-callbackerr-cookie); only works with synchronous stores (that is, the default `MemoryCookieStore`).
#### `.getCookies(currentUrl[, options][, callback(err, cookies)])`
Retrieve the list of cookies that can be sent in a Cookie header for the current URL. Returns a promise if a callback is not provided.
Returns an array of `Cookie` objects, sorted by default using [`cookieCompare`](#cookiecomparea-b).
If an error is encountered it's passed as `err` to the callback, otherwise an array of `Cookie` objects is passed. The array is sorted with `cookieCompare()` unless the `{sort:false}` option is given.
The `options` object can be omitted and can have the following properties:
- _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects `HttpOnly` cookies.
- _secure_ - boolean - autodetect from URL - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` then this is defaulted to `true`, otherwise `false`.
- _now_ - Date - default `new Date()` - what to use for the creation or access time of cookies
- _expire_ - boolean - default `true` - perform expiry-time checking of cookies and asynchronously remove expired cookies from the store. Using `false` returns expired cookies and does **not** remove them from the store (which is potentially useful for replaying Set-Cookie headers).
- _allPaths_ - boolean - default `false` - if `true`, do not scope cookies by path. The default uses RFC-compliant path scoping. **Note**: may not be supported by the underlying store (the default `MemoryCookieStore` supports it).
- _sameSiteContext_ - string - default unset - Set this to `'none'`, `'lax'`, or `'strict'` to enforce SameSite cookies upon retrieval. See [SameSite Cookies](#samesite-cookies) below.
- _sort_ - boolean - whether to sort the list of cookies.
The `.lastAccessed` property of the returned cookies will have been updated.
#### `.getCookiesSync(currentUrl, [{options}])`
Synchronous version of [`getCookies`](#getcookiescurrenturl-options-callbackerr-cookies); only works with synchronous stores (for example, the default `MemoryCookieStore`).
#### `.getCookieString(...)`
Accepts the same options as [`.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies) but returns a string suitable for a Cookie header rather than an Array.
#### `.getCookieStringSync(...)`
Synchronous version of [`getCookieString`](#getcookiestring); only works with synchronous stores (for example, the default `MemoryCookieStore`).
#### `.getSetCookieStrings(...)`
Returns an array of strings suitable for **Set-Cookie** headers. Accepts the same options as [`.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies). Simply maps the cookie array via `.toString()`.
#### `.getSetCookieStringsSync(...)`
Synchronous version of [`getSetCookieStrings`](#getsetcookiestrings); only works with synchronous stores (for example, the default `MemoryCookieStore`).
#### `.serialize([callback(err, serializedObject)])`
Returns a promise if a callback is not provided.
Serialize the Jar if the underlying store supports `.getAllCookies`.
> **NOTE**: Custom `Cookie` properties are discarded. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array.
See [Serialization Format](#serialization-format).
#### `.serializeSync()`
Synchronous version of [`serialize`](#serializecallbackerr-serializedobject); only works with synchronous stores (for example, the default `MemoryCookieStore`).
#### `.toJSON()`
Alias of [`.serializeSync()`](#serializesync) for the convenience of `JSON.stringify(cookiejar)`.
#### `CookieJar.deserialize(serialized[, store][, callback(err, object)])`
A new Jar is created and the serialized Cookies are added to the underlying store. Each `Cookie` is added via `store.putCookie` in the order in which they appear in the serialization. A promise is returned if a callback is not provided.
The `store` argument is optional, but should be an instance of `Store`. By default, a new instance of `MemoryCookieStore` is created.
As a convenience, if `serialized` is a string, it is passed through `JSON.parse` first.
#### `CookieJar.deserializeSync(serialized[, store])`
Sync version of [`.deserialize`](#cookiejardeserializeserialized-store-callbackerr-object); only works with synchronous stores (for example, the default `MemoryCookieStore`).
#### `CookieJar.fromJSON(string)`
Alias of [`.deserializeSync`](#cookiejardeserializesyncserialized-store) to provide consistency with [`Cookie.fromJSON()`](#cookiefromjsonstrorobj).
#### `.clone([store][, callback(err, cloned))`
Produces a deep clone of this jar. Modifications to the original do not affect the clone, and vice versa. Returns a promise if a callback is not provided.
The `store` argument is optional, but should be an instance of `Store`. By default, a new instance of `MemoryCookieStore` is created. Transferring between store types is supported so long as the source implements `.getAllCookies()` and the destination implements `.putCookie()`.
#### `.cloneSync([store])`
Synchronous version of [`.clone`](#clonestore-callbackerr-cloned), returning a new `CookieJar` instance.
The `store` argument is optional, but must be a _synchronous_ `Store` instance if specified. If not passed, a new instance of `MemoryCookieStore` is used.
The _source_ and _destination_ must both be synchronous `Store`s. If one or both stores are asynchronous, use `.clone` instead. Recall that `MemoryCookieStore` supports both synchronous and asynchronous API calls.
#### `.removeAllCookies([callback(err)])`
Removes all cookies from the jar. Returns a promise if a callback is not provided.
This is a new backwards-compatible feature of `tough-cookie` version 2.5, so not all Stores will implement it efficiently. For Stores that do not implement `removeAllCookies`, the fallback is to call `removeCookie` after `getAllCookies`. If `getAllCookies` fails or isn't implemented in the Store, that error is returned. If one or more of the `removeCookie` calls fail, only the first error is returned.
#### `.removeAllCookiesSync()`
Sync version of [`.removeAllCookies()`](#removeallcookiescallbackerr); only works with synchronous stores (for example, the default `MemoryCookieStore`).
### Store
Base class for CookieJar stores. Available as `tough.Store`.
### Store API
The storage model for each `CookieJar` instance can be replaced with a custom implementation. The default is `MemoryCookieStore` which can be found in [`lib/memstore.js`](https://github.com/salesforce/tough-cookie/blob/master/lib/memstore.js). The API uses continuation-passing-style to allow for asynchronous stores.
Stores should inherit from the base `Store` class, which is available as a top-level export.
Stores are asynchronous by default, but if `store.synchronous` is set to `true`, then the `*Sync` methods of the containing `CookieJar` can be used.
All `domain` parameters are normalized before calling.
The Cookie store must have all of the following methods. Note that asynchronous implementations **must** support callback parameters.
#### `store.findCookie(domain, path, key, callback(err, cookie))`
Retrieve a cookie with the given domain, path, and key (name). The RFC maintains that exactly one of these cookies should exist in a store. If the store is using versioning, this means that the latest or newest such cookie should be returned.
Callback takes an error and the resulting `Cookie` object. If no cookie is found then `null` MUST be passed instead (that is, not an error).
#### `store.findCookies(domain, path, allowSpecialUseDomain, callback(err, cookies))`
Locates cookies matching the given domain and path. This is most often called in the context of [`cookiejar.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies).
If no cookies are found, the callback MUST be passed an empty array.
The resulting list is checked for applicability to the current request according to the RFC (domain-match, path-match, http-only-flag, secure-flag, expiry, and so on), so it's OK to use an optimistic search algorithm when implementing this method. However, the search algorithm used SHOULD try to find cookies that `domainMatch()` the domain and `pathMatch()` the path in order to limit the amount of checking that needs to be done.
As of version 0.9.12, the `allPaths` option to `cookiejar.getCookies()` above causes the path here to be `null`. If the path is `null`, path-matching MUST NOT be performed (that is, domain-matching only).
#### `store.putCookie(cookie, callback(err))`
Adds a new cookie to the store. The implementation SHOULD replace any existing cookie with the same `.domain`, `.path`, and `.key` properties. Depending on the nature of the implementation, it's possible that between the call to `fetchCookie` and `putCookie` that a duplicate `putCookie` can occur.
The `cookie` object MUST NOT be modified; as the caller has already updated the `.creation` and `.lastAccessed` properties.
Pass an error if the cookie cannot be stored.
#### `store.updateCookie(oldCookie, newCookie, callback(err))`
Update an existing cookie. The implementation MUST update the `.value` for a cookie with the same `domain`, `.path`, and `.key`. The implementation SHOULD check that the old value in the store is equivalent to `oldCookie` - how the conflict is resolved is up to the store.
The `.lastAccessed` property is always different between the two objects (to the precision possible via JavaScript's clock). Both `.creation` and `.creationIndex` are guaranteed to be the same. Stores MAY ignore or defer the `.lastAccessed` change at the cost of affecting how cookies are selected for automatic deletion (for example, least-recently-used, which is up to the store to implement).
Stores may wish to optimize changing the `.value` of the cookie in the store versus storing a new cookie. If the implementation doesn't define this method, a stub that calls [`putCookie`](#storeputcookiecookie-callbackerr) is added to the store object.
The `newCookie` and `oldCookie` objects MUST NOT be modified.
Pass an error if the newCookie cannot be stored.
#### `store.removeCookie(domain, path, key, callback(err))`
Remove a cookie from the store (see notes on [`findCookie`](#storefindcookiedomain-path-key-callbackerr-cookie) about the uniqueness constraint).
The implementation MUST NOT pass an error if the cookie doesn't exist, and only pass an error due to the failure to remove an existing cookie.
#### `store.removeCookies(domain, path, callback(err))`
Removes matching cookies from the store. The `path` parameter is optional and if missing, means all paths in a domain should be removed.
Pass an error ONLY if removing any existing cookies failed.
#### `store.removeAllCookies(callback(err))`
_Optional_. Removes all cookies from the store.
Pass an error if one or more cookies can't be removed.
#### `store.getAllCookies(callback(err, cookies))`
_Optional_. Produces an `Array` of all cookies during [`jar.serialize()`](#serializecallbackerr-serializedobject). The items in the array can be true `Cookie` objects or generic `Object`s with the [Serialization Format](#serialization-format) data structure.
Cookies SHOULD be returned in creation order to preserve sorting via [`compareCookie()`](#cookiecomparea-b). For reference, `MemoryCookieStore` sorts by `.creationIndex` since it uses true `Cookie` objects internally. If you don't return the cookies in creation order, they'll still be sorted by creation time, but this only has a precision of 1-ms. See `cookieCompare` for more detail.
Pass an error if retrieval fails.
**Note**: Not all Stores can implement this due to technical limitations, so it is optional.
### MemoryCookieStore
Inherits from `Store`.
A just-in-memory CookieJar synchronous store implementation, used by default. Despite being a synchronous implementation, it's usable with both the synchronous and asynchronous forms of the `CookieJar` API. Supports serialization, `getAllCookies`, and `removeAllCookies`.
### Community Cookie Stores
These are some Store implementations authored and maintained by the community. They aren't official and we don't vouch for them but you may be interested to have a look:
- [`db-cookie-store`](https://github.com/JSBizon/db-cookie-store): SQL including SQLite-based databases
- [`file-cookie-store`](https://github.com/JSBizon/file-cookie-store): Netscape cookie file format on disk
- [`redis-cookie-store`](https://github.com/benkroeger/redis-cookie-store): Redis
- [`tough-cookie-filestore`](https://github.com/mitsuru/tough-cookie-filestore): JSON on disk
- [`tough-cookie-web-storage-store`](https://github.com/exponentjs/tough-cookie-web-storage-store): DOM localStorage and sessionStorage
## Serialization Format
**NOTE**: If you want to have custom `Cookie` properties serialized, add the property name to `Cookie.serializableProperties`.
```js
{
// The version of tough-cookie that serialized this jar.
version: 'tough-cookie@1.x.y',
// add the store type, to make humans happy:
storeType: 'MemoryCookieStore',
// CookieJar configuration:
rejectPublicSuffixes: true,
// ... future items go here
// Gets filled from jar.store.getAllCookies():
cookies: [
{
key: 'string',
value: 'string',
// ...
/* other Cookie.serializableProperties go here */
}
]
}
```
## RFC 6265bis
Support for RFC 6265bis revision 02 is being developed. Since this is a bit of an omnibus revision to the RFC 6252, support is broken up into the functional areas.
### Leave Secure Cookies Alone
Not yet supported.
This change makes it so that if a cookie is sent from the server to the client with a `Secure` attribute, the channel must also be secure or the cookie is ignored.
### SameSite Cookies
Supported.
This change makes it possible for servers, and supporting clients, to mitigate certain types of CSRF attacks by disallowing `SameSite` cookies from being sent cross-origin.
On the Cookie object itself, you can get or set the `.sameSite` attribute, which is serialized into the `SameSite=` cookie attribute. When unset or `undefined`, no `SameSite=` attribute is serialized. The valid values of this attribute are `'none'`, `'lax'`, or `'strict'`. Other values are serialized as-is.
When parsing cookies with a `SameSite` cookie attribute, values other than `'lax'` or `'strict'` are parsed as `'none'`. For example, `SomeCookie=SomeValue; SameSite=garbage` parses so that `cookie.sameSite === 'none'`.
In order to support SameSite cookies, you must provide a `sameSiteContext` option to _both_ `setCookie` and `getCookies`. Valid values for this option are just like for the Cookie object, but have particular meanings:
1. `'strict'` mode - If the request is on the same "site for cookies" (see the RFC draft for more information), pass this option to add a layer of defense against CSRF.
2. `'lax'` mode - If the request is from another site, _but_ is directly because of navigation by the user, such as, `<link type=prefetch>` or `<a href="...">`, pass `sameSiteContext: 'lax'`.
3. `'none'` - Otherwise, pass `sameSiteContext: 'none'` (this indicates a cross-origin request).
4. unset/`undefined` - SameSite **is not** be enforced! This can be a valid use-case for when CSRF isn't in the threat model of the system being built.
It is highly recommended that you read RFC 6265bis for fine details on SameSite cookies. In particular [Section 8.8](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-02##section-8.8) discusses security considerations and defense in depth.
### Cookie Prefixes
Supported.
Cookie prefixes are a way to indicate that a given cookie was set with a set of attributes simply by inspecting the first few characters of the cookie's name.
Cookie prefixes are defined in [Section 4.1.3 of 6265bis](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03##section-4.1.3).
Two prefixes are defined:
1. `"__Secure-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "\_\_Secure-", then the cookie was set with a "Secure" attribute.
2. `"__Host-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "\_\_Host-", then the cookie was set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute.
If `prefixSecurity` is enabled for `CookieJar`, then cookies that match the prefixes defined above but do not obey the attribute restrictions are not added.
You can define this functionality by passing in the `prefixSecurity` option to `CookieJar`. It can be one of 3 values:
1. `silent`: Enable cookie prefix checking but silently fail to add the cookie if conditions are not met. Default.
2. `strict`: Enable cookie prefix checking and error out if conditions are not met.
3. `unsafe-disabled`: Disable cookie prefix checking.
Note that if `ignoreError` is passed in as `true` then the error is silent regardless of the `prefixSecurity` option (assuming it's enabled).
## Copyright and License
BSD-3-Clause:
```text
Copyright (c) 2015, Salesforce.com, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Salesforce.com nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
```
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,242 @@
/*!
* Copyright (c) 2015, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
const { fromCallback } = require("universalify");
const Store = require("./store").Store;
const permuteDomain = require("./permuteDomain").permuteDomain;
const pathMatch = require("./pathMatch").pathMatch;
const { getCustomInspectSymbol, getUtilInspect } = require("./utilHelper");
class MemoryCookieStore extends Store {
constructor() {
super();
this.synchronous = true;
this.idx = Object.create(null);
const customInspectSymbol = getCustomInspectSymbol();
if (customInspectSymbol) {
this[customInspectSymbol] = this.inspect;
}
}
inspect() {
const util = { inspect: getUtilInspect(inspectFallback) };
return `{ idx: ${util.inspect(this.idx, false, 2)} }`;
}
findCookie(domain, path, key, cb) {
if (!this.idx[domain]) {
return cb(null, undefined);
}
if (!this.idx[domain][path]) {
return cb(null, undefined);
}
return cb(null, this.idx[domain][path][key] || null);
}
findCookies(domain, path, allowSpecialUseDomain, cb) {
const results = [];
if (typeof allowSpecialUseDomain === "function") {
cb = allowSpecialUseDomain;
allowSpecialUseDomain = true;
}
if (!domain) {
return cb(null, []);
}
let pathMatcher;
if (!path) {
// null means "all paths"
pathMatcher = function matchAll(domainIndex) {
for (const curPath in domainIndex) {
const pathIndex = domainIndex[curPath];
for (const key in pathIndex) {
results.push(pathIndex[key]);
}
}
};
} else {
pathMatcher = function matchRFC(domainIndex) {
//NOTE: we should use path-match algorithm from S5.1.4 here
//(see : https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/canonical_cookie.cc#L299)
Object.keys(domainIndex).forEach(cookiePath => {
if (pathMatch(path, cookiePath)) {
const pathIndex = domainIndex[cookiePath];
for (const key in pathIndex) {
results.push(pathIndex[key]);
}
}
});
};
}
const domains = permuteDomain(domain, allowSpecialUseDomain) || [domain];
const idx = this.idx;
domains.forEach(curDomain => {
const domainIndex = idx[curDomain];
if (!domainIndex) {
return;
}
pathMatcher(domainIndex);
});
cb(null, results);
}
putCookie(cookie, cb) {
if (!this.idx[cookie.domain]) {
this.idx[cookie.domain] = Object.create(null);
}
if (!this.idx[cookie.domain][cookie.path]) {
this.idx[cookie.domain][cookie.path] = Object.create(null);
}
this.idx[cookie.domain][cookie.path][cookie.key] = cookie;
cb(null);
}
updateCookie(oldCookie, newCookie, cb) {
// updateCookie() may avoid updating cookies that are identical. For example,
// lastAccessed may not be important to some stores and an equality
// comparison could exclude that field.
this.putCookie(newCookie, cb);
}
removeCookie(domain, path, key, cb) {
if (
this.idx[domain] &&
this.idx[domain][path] &&
this.idx[domain][path][key]
) {
delete this.idx[domain][path][key];
}
cb(null);
}
removeCookies(domain, path, cb) {
if (this.idx[domain]) {
if (path) {
delete this.idx[domain][path];
} else {
delete this.idx[domain];
}
}
return cb(null);
}
removeAllCookies(cb) {
this.idx = Object.create(null);
return cb(null);
}
getAllCookies(cb) {
const cookies = [];
const idx = this.idx;
const domains = Object.keys(idx);
domains.forEach(domain => {
const paths = Object.keys(idx[domain]);
paths.forEach(path => {
const keys = Object.keys(idx[domain][path]);
keys.forEach(key => {
if (key !== null) {
cookies.push(idx[domain][path][key]);
}
});
});
});
// Sort by creationIndex so deserializing retains the creation order.
// When implementing your own store, this SHOULD retain the order too
cookies.sort((a, b) => {
return (a.creationIndex || 0) - (b.creationIndex || 0);
});
cb(null, cookies);
}
}
[
"findCookie",
"findCookies",
"putCookie",
"updateCookie",
"removeCookie",
"removeCookies",
"removeAllCookies",
"getAllCookies"
].forEach(name => {
MemoryCookieStore.prototype[name] = fromCallback(
MemoryCookieStore.prototype[name]
);
});
exports.MemoryCookieStore = MemoryCookieStore;
function inspectFallback(val) {
const domains = Object.keys(val);
if (domains.length === 0) {
return "[Object: null prototype] {}";
}
let result = "[Object: null prototype] {\n";
Object.keys(val).forEach((domain, i) => {
result += formatDomain(domain, val[domain]);
if (i < domains.length - 1) {
result += ",";
}
result += "\n";
});
result += "}";
return result;
}
function formatDomain(domainName, domainValue) {
const indent = " ";
let result = `${indent}'${domainName}': [Object: null prototype] {\n`;
Object.keys(domainValue).forEach((path, i, paths) => {
result += formatPath(path, domainValue[path]);
if (i < paths.length - 1) {
result += ",";
}
result += "\n";
});
result += `${indent}}`;
return result;
}
function formatPath(pathName, pathValue) {
const indent = " ";
let result = `${indent}'${pathName}': [Object: null prototype] {\n`;
Object.keys(pathValue).forEach((cookieName, i, cookieNames) => {
const cookie = pathValue[cookieName];
result += ` ${cookieName}: ${cookie.inspect()}`;
if (i < cookieNames.length - 1) {
result += ",";
}
result += "\n";
});
result += `${indent}}`;
return result;
}
exports.inspectFallback = inspectFallback;
@@ -0,0 +1,61 @@
/*!
* Copyright (c) 2015, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
/*
* "A request-path path-matches a given cookie-path if at least one of the
* following conditions holds:"
*/
function pathMatch(reqPath, cookiePath) {
// "o The cookie-path and the request-path are identical."
if (cookiePath === reqPath) {
return true;
}
const idx = reqPath.indexOf(cookiePath);
if (idx === 0) {
// "o The cookie-path is a prefix of the request-path, and the last
// character of the cookie-path is %x2F ("/")."
if (cookiePath.substr(-1) === "/") {
return true;
}
// " o The cookie-path is a prefix of the request-path, and the first
// character of the request-path that is not included in the cookie- path
// is a %x2F ("/") character."
if (reqPath.substr(cookiePath.length, 1) === "/") {
return true;
}
}
return false;
}
exports.pathMatch = pathMatch;
@@ -0,0 +1,65 @@
/*!
* Copyright (c) 2015, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
const pubsuffix = require("./pubsuffix-psl");
// Gives the permutation of all possible domainMatch()es of a given domain. The
// array is in shortest-to-longest order. Handy for indexing.
function permuteDomain(domain, allowSpecialUseDomain) {
const pubSuf = pubsuffix.getPublicSuffix(domain, {
allowSpecialUseDomain: allowSpecialUseDomain
});
if (!pubSuf) {
return null;
}
if (pubSuf == domain) {
return [domain];
}
// Nuke trailing dot
if (domain.slice(-1) == ".") {
domain = domain.slice(0, -1);
}
const prefix = domain.slice(0, -(pubSuf.length + 1)); // ".example.com"
const parts = prefix.split(".").reverse();
let cur = pubSuf;
const permutations = [cur];
while (parts.length) {
cur = `${parts.shift()}.${cur}`;
permutations.push(cur);
}
return permutations;
}
exports.permuteDomain = permuteDomain;
@@ -0,0 +1,73 @@
/*!
* Copyright (c) 2018, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
const psl = require("psl");
// RFC 6761
const SPECIAL_USE_DOMAINS = [
"local",
"example",
"invalid",
"localhost",
"test"
];
const SPECIAL_TREATMENT_DOMAINS = ["localhost", "invalid"];
function getPublicSuffix(domain, options = {}) {
const domainParts = domain.split(".");
const topLevelDomain = domainParts[domainParts.length - 1];
const allowSpecialUseDomain = !!options.allowSpecialUseDomain;
const ignoreError = !!options.ignoreError;
if (allowSpecialUseDomain && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) {
if (domainParts.length > 1) {
const secondLevelDomain = domainParts[domainParts.length - 2];
// In aforementioned example, the eTLD/pubSuf will be apple.localhost
return `${secondLevelDomain}.${topLevelDomain}`;
} else if (SPECIAL_TREATMENT_DOMAINS.includes(topLevelDomain)) {
// For a single word special use domain, e.g. 'localhost' or 'invalid', per RFC 6761,
// "Application software MAY recognize {localhost/invalid} names as special, or
// MAY pass them to name resolution APIs as they would for other domain names."
return `${topLevelDomain}`;
}
}
if (!ignoreError && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) {
throw new Error(
`Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`
);
}
return psl.get(domain);
}
exports.getPublicSuffix = getPublicSuffix;
@@ -0,0 +1,76 @@
/*!
* Copyright (c) 2015, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
/*jshint unused:false */
class Store {
constructor() {
this.synchronous = false;
}
findCookie(domain, path, key, cb) {
throw new Error("findCookie is not implemented");
}
findCookies(domain, path, allowSpecialUseDomain, cb) {
throw new Error("findCookies is not implemented");
}
putCookie(cookie, cb) {
throw new Error("putCookie is not implemented");
}
updateCookie(oldCookie, newCookie, cb) {
// recommended default implementation:
// return this.putCookie(newCookie, cb);
throw new Error("updateCookie is not implemented");
}
removeCookie(domain, path, key, cb) {
throw new Error("removeCookie is not implemented");
}
removeCookies(domain, path, cb) {
throw new Error("removeCookies is not implemented");
}
removeAllCookies(cb) {
throw new Error("removeAllCookies is not implemented");
}
getAllCookies(cb) {
throw new Error(
"getAllCookies is not implemented (therefore jar cannot be serialized)"
);
}
}
exports.Store = Store;
@@ -0,0 +1,39 @@
function requireUtil() {
try {
// eslint-disable-next-line no-restricted-modules
return require("util");
} catch (e) {
return null;
}
}
// for v10.12.0+
function lookupCustomInspectSymbol() {
return Symbol.for("nodejs.util.inspect.custom");
}
// for older node environments
function tryReadingCustomSymbolFromUtilInspect(options) {
const _requireUtil = options.requireUtil || requireUtil;
const util = _requireUtil();
return util ? util.inspect.custom : null;
}
exports.getUtilInspect = function getUtilInspect(fallback, options = {}) {
const _requireUtil = options.requireUtil || requireUtil;
const util = _requireUtil();
return function inspect(value, showHidden, depth) {
return util ? util.inspect(value, showHidden, depth) : fallback(value);
};
};
exports.getCustomInspectSymbol = function getCustomInspectSymbol(options = {}) {
const _lookupCustomInspectSymbol =
options.lookupCustomInspectSymbol || lookupCustomInspectSymbol;
// get custom inspect symbol for node environments
return (
_lookupCustomInspectSymbol() ||
tryReadingCustomSymbolFromUtilInspect(options)
);
};
@@ -0,0 +1,110 @@
/* ************************************************************************************
Extracted from check-types.js
https://gitlab.com/philbooth/check-types.js
MIT License
Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Phil Booth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
************************************************************************************ */
"use strict";
/* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */
const toString = Object.prototype.toString;
function isFunction(data) {
return typeof data === "function";
}
function isNonEmptyString(data) {
return isString(data) && data !== "";
}
function isDate(data) {
return isInstanceStrict(data, Date) && isInteger(data.getTime());
}
function isEmptyString(data) {
return data === "" || (data instanceof String && data.toString() === "");
}
function isString(data) {
return typeof data === "string" || data instanceof String;
}
function isObject(data) {
return toString.call(data) === "[object Object]";
}
function isInstanceStrict(data, prototype) {
try {
return data instanceof prototype;
} catch (error) {
return false;
}
}
function isUrlStringOrObject(data) {
return (
isNonEmptyString(data) ||
(isObject(data) &&
"hostname" in data &&
"pathname" in data &&
"protocol" in data) ||
isInstanceStrict(data, URL)
);
}
function isInteger(data) {
return typeof data === "number" && data % 1 === 0;
}
/* End validation functions */
function validate(bool, cb, options) {
if (!isFunction(cb)) {
options = cb;
cb = null;
}
if (!isObject(options)) options = { Error: "Failed Check" };
if (!bool) {
if (cb) {
cb(new ParameterError(options));
} else {
throw new ParameterError(options);
}
}
}
class ParameterError extends Error {
constructor(...params) {
super(...params);
}
}
exports.ParameterError = ParameterError;
exports.isFunction = isFunction;
exports.isNonEmptyString = isNonEmptyString;
exports.isDate = isDate;
exports.isEmptyString = isEmptyString;
exports.isString = isString;
exports.isObject = isObject;
exports.isUrlStringOrObject = isUrlStringOrObject;
exports.validate = validate;
@@ -0,0 +1,2 @@
// generated by genversion
module.exports = '4.1.4'
@@ -0,0 +1,110 @@
{
"author": {
"name": "Jeremy Stashewsky",
"email": "jstash@gmail.com",
"website": "https://github.com/stash"
},
"contributors": [
{
"name": "Ivan Nikulin",
"website": "https://github.com/inikulin"
},
{
"name": "Shivan Kaul Sahib",
"website": "https://github.com/ShivanKaul"
},
{
"name": "Clint Ruoho",
"website": "https://github.com/ruoho"
},
{
"name": "Ian Livingstone",
"website": "https://github.com/ianlivingstone"
},
{
"name": "Andrew Waterman",
"website": "https://github.com/awaterma"
},
{
"name": "Michael de Libero ",
"website": "https://github.com/medelibero-sfdc"
},
{
"name": "Jonathan Stewmon",
"website": "https://github.com/jstewmon"
},
{
"name": "Miguel Roncancio",
"website": "https://github.com/miggs125"
},
{
"name": "Sebastian Mayr",
"website": "https://github.com/Sebmaster"
},
{
"name": "Alexander Savin",
"website": "https://github.com/apsavin"
},
{
"name": "Lalit Kapoor",
"website": "https://github.com/lalitkapoor"
},
{
"name": "Sam Thompson",
"website": "https://github.com/sambthompson"
}
],
"license": "BSD-3-Clause",
"name": "tough-cookie",
"description": "RFC6265 Cookies and Cookie Jar for node.js",
"keywords": [
"HTTP",
"cookie",
"cookies",
"set-cookie",
"cookiejar",
"jar",
"RFC6265",
"RFC2965"
],
"version": "4.1.4",
"homepage": "https://github.com/salesforce/tough-cookie",
"repository": {
"type": "git",
"url": "git://github.com/salesforce/tough-cookie.git"
},
"bugs": {
"url": "https://github.com/salesforce/tough-cookie/issues"
},
"main": "./lib/cookie",
"files": [
"lib"
],
"scripts": {
"version": "genversion lib/version.js && git add lib/version.js",
"test": "vows test/*_test.js && npm run eslint",
"cover": "nyc --reporter=lcov --reporter=html vows test/*_test.js",
"eslint": "eslint --env node --ext .js .",
"prettier": "prettier '**/*.{json,ts,yaml,md}'",
"format": "npm run eslint -- --fix"
},
"engines": {
"node": ">=6"
},
"devDependencies": {
"async": "^2.6.2",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.2.0",
"eslint-plugin-prettier": "^3.0.1",
"genversion": "^2.1.0",
"nyc": "^14.0.0",
"prettier": "^1.17.0",
"vows": "^0.8.2"
},
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
}
}
+61
View File
@@ -0,0 +1,61 @@
{
"name": "@cypress/request-promise",
"version": "5.0.0",
"description": "Cypress's fork of the simplified HTTP request client 'request' with Promise support. Powered by Bluebird.",
"keywords": [
"xhr",
"http",
"https",
"promise",
"request",
"then",
"thenable",
"bluebird"
],
"main": "./lib/rp.js",
"scripts": {
"test": "./node_modules/.bin/gulp ci",
"test-publish": "./node_modules/.bin/gulp ci-no-cov",
"publish-please": "publish-please"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cypress-io/request-promise.git"
},
"author": "Nicolai Kamenzky (https://github.com/analog-nico)",
"license": "ISC",
"bugs": {
"url": "https://github.com/cypress-io/request-promise/issues"
},
"homepage": "https://github.com/cypress-io/request-promise#readme",
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"request-promise-core": "1.1.3",
"bluebird": "^3.5.0",
"stealthy-require": "^1.1.1",
"tough-cookie": "^4.1.3"
},
"peerDependencies": {
"@cypress/request": "^3.0.0"
},
"devDependencies": {
"body-parser": "~1.15.2",
"chai": "~3.5.0",
"chalk": "~1.1.3",
"gulp": "~3.9.1",
"gulp-coveralls": "~0.1.4",
"gulp-eslint": "~2.1.0",
"gulp-istanbul": "~1.0.0",
"gulp-mocha": "~2.2.0",
"lodash": "~4.13.1",
"publish-please": "~2.1.4",
"@cypress/request": "^3.0.0",
"rimraf": "~2.5.3",
"run-sequence": "~1.2.2"
},
"resolutions": {
"chalk": "~1.1.3"
}
}
+55
View File
@@ -0,0 +1,55 @@
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:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
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
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
+1032
View File
File diff suppressed because it is too large. Load diff
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2010-2012 Mikeal Rogers
//
// 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.
'use strict'
var extend = require('extend')
var cookies = require('./lib/cookies')
var helpers = require('./lib/helpers')
var paramsHaveRequestBody = helpers.paramsHaveRequestBody
// organize params for patch, post, put, head, del
function initParams (uri, options, callback) {
if (typeof options === 'function') {
callback = options
}
var params = {}
if (options !== null && typeof options === 'object') {
extend(params, options, {uri: uri})
} else if (typeof uri === 'string') {
extend(params, {uri: uri})
} else {
extend(params, uri)
}
params.callback = callback || params.callback
return params
}
function request (uri, options, callback) {
if (typeof uri === 'undefined') {
throw new Error('undefined is not a valid uri or options object.')
}
var params = initParams(uri, options, callback)
if (params.method === 'HEAD' && paramsHaveRequestBody(params)) {
throw new Error('HTTP HEAD requests MUST NOT include a request body.')
}
return new request.Request(params)
}
function verbFunc (verb) {
var method = verb.toUpperCase()
return function (uri, options, callback) {
var params = initParams(uri, options, callback)
params.method = method
return request(params, params.callback)
}
}
// define like this to please codeintel/intellisense IDEs
request.get = verbFunc('get')
request.head = verbFunc('head')
request.options = verbFunc('options')
request.post = verbFunc('post')
request.put = verbFunc('put')
request.patch = verbFunc('patch')
request.del = verbFunc('delete')
request['delete'] = verbFunc('delete')
request.jar = function (store) {
return cookies.jar(store)
}
request.cookie = function (str) {
return cookies.parse(str)
}
function wrapRequestMethod (method, options, requester, verb) {
return function (uri, opts, callback) {
var params = initParams(uri, opts, callback)
var target = {}
extend(true, target, options, params)
target.pool = params.pool || options.pool
if (verb) {
target.method = verb.toUpperCase()
}
if (typeof requester === 'function') {
method = requester
}
return method(target, target.callback)
}
}
request.defaults = function (options, requester) {
var self = this
options = options || {}
if (typeof options === 'function') {
requester = options
options = {}
}
var defaults = wrapRequestMethod(self, options, requester)
var verbs = ['get', 'head', 'post', 'put', 'patch', 'del', 'delete']
verbs.forEach(function (verb) {
defaults[verb] = wrapRequestMethod(self[verb], options, requester, verb)
})
defaults.cookie = wrapRequestMethod(self.cookie, options, requester)
defaults.jar = self.jar
defaults.defaults = self.defaults
return defaults
}
request.forever = function (agentOptions, optionsArg) {
var options = {}
if (optionsArg) {
extend(options, optionsArg)
}
if (agentOptions) {
options.agentOptions = agentOptions
}
options.forever = true
return request.defaults(options)
}
// Exports
module.exports = request
request.Request = require('./request')
request.initParams = initParams
// Backwards compatibility for request.debug
Object.defineProperty(request, 'debug', {
enumerable: true,
get: function () {
return request.Request.debug
},
set: function (debug) {
request.Request.debug = debug
}
})
+167
View File
@@ -0,0 +1,167 @@
'use strict'
var caseless = require('caseless')
var { v4: uuid } = require('uuid')
var helpers = require('./helpers')
var md5 = helpers.md5
var toBase64 = helpers.toBase64
function Auth (request) {
// define all public properties here
this.request = request
this.hasAuth = false
this.sentAuth = false
this.bearerToken = null
this.user = null
this.pass = null
}
Auth.prototype.basic = function (user, pass, sendImmediately) {
var self = this
if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
self.request.emit('error', new Error('auth() received invalid user or password'))
}
self.user = user
self.pass = pass
self.hasAuth = true
var header = user + ':' + (pass || '')
if (sendImmediately || typeof sendImmediately === 'undefined') {
var authHeader = 'Basic ' + toBase64(header)
self.sentAuth = true
return authHeader
}
}
Auth.prototype.bearer = function (bearer, sendImmediately) {
var self = this
self.bearerToken = bearer
self.hasAuth = true
if (sendImmediately || typeof sendImmediately === 'undefined') {
if (typeof bearer === 'function') {
bearer = bearer()
}
var authHeader = 'Bearer ' + (bearer || '')
self.sentAuth = true
return authHeader
}
}
Auth.prototype.digest = function (method, path, authHeader) {
// TODO: More complete implementation of RFC 2617.
// - handle challenge.domain
// - support qop="auth-int" only
// - handle Authentication-Info (not necessarily?)
// - check challenge.stale (not necessarily?)
// - increase nc (not necessarily?)
// For reference:
// http://tools.ietf.org/html/rfc2617#section-3
// https://github.com/bagder/curl/blob/master/lib/http_digest.c
var self = this
var challenge = {}
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
while (true) {
var match = re.exec(authHeader)
if (!match) {
break
}
challenge[match[1]] = match[2] || match[3]
}
/**
* RFC 2617: handle both MD5 and MD5-sess algorithms.
*
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
* HA1=MD5(username:realm:password)
* If the algorithm directive's value is "MD5-sess", then HA1 is
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
*/
var ha1Compute = function (algorithm, user, realm, pass, nonce, cnonce) {
var ha1 = md5(user + ':' + realm + ':' + pass)
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
return md5(ha1 + ':' + nonce + ':' + cnonce)
} else {
return ha1
}
}
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
var nc = qop && '00000001'
var cnonce = qop && uuid().replace(/-/g, '')
var ha1 = ha1Compute(challenge.algorithm, self.user, challenge.realm, self.pass, challenge.nonce, cnonce)
var ha2 = md5(method + ':' + path)
var digestResponse = qop
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
var authValues = {
username: self.user,
realm: challenge.realm,
nonce: challenge.nonce,
uri: path,
qop: qop,
response: digestResponse,
nc: nc,
cnonce: cnonce,
algorithm: challenge.algorithm,
opaque: challenge.opaque
}
authHeader = []
for (var k in authValues) {
if (authValues[k]) {
if (k === 'qop' || k === 'nc' || k === 'algorithm') {
authHeader.push(k + '=' + authValues[k])
} else {
authHeader.push(k + '="' + authValues[k] + '"')
}
}
}
authHeader = 'Digest ' + authHeader.join(', ')
self.sentAuth = true
return authHeader
}
Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
var self = this
var request = self.request
var authHeader
if (bearer === undefined && user === undefined) {
self.request.emit('error', new Error('no auth mechanism defined'))
} else if (bearer !== undefined) {
authHeader = self.bearer(bearer, sendImmediately)
} else {
authHeader = self.basic(user, pass, sendImmediately)
}
if (authHeader) {
request.setHeader('authorization', authHeader)
}
}
Auth.prototype.onResponse = function (response) {
var self = this
var request = self.request
if (!self.hasAuth || self.sentAuth) { return null }
var c = caseless(response.headers)
var authHeader = c.get('www-authenticate')
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
request.debug('reauth', authVerb)
switch (authVerb) {
case 'basic':
return self.basic(self.user, self.pass, true)
case 'bearer':
return self.bearer(self.bearerToken, true)
case 'digest':
return self.digest(request.method, request.path, authHeader)
}
}
exports.Auth = Auth
+38
View File
@@ -0,0 +1,38 @@
'use strict'
var tough = require('tough-cookie')
var Cookie = tough.Cookie
var CookieJar = tough.CookieJar
exports.parse = function (str) {
if (str && str.uri) {
str = str.uri
}
if (typeof str !== 'string') {
throw new Error('The cookie function only accepts STRING as param')
}
return Cookie.parse(str, {loose: true})
}
// Adapt the sometimes-Async api of tough.CookieJar to our requirements
function RequestJar (store) {
var self = this
self._jar = new CookieJar(store, {looseMode: true})
}
RequestJar.prototype.setCookie = function (cookieOrStr, uri, options) {
var self = this
return self._jar.setCookieSync(cookieOrStr, uri, options || {})
}
RequestJar.prototype.getCookieString = function (uri) {
var self = this
return self._jar.getCookieStringSync(uri)
}
RequestJar.prototype.getCookies = function (uri) {
var self = this
return self._jar.getCookiesSync(uri)
}
exports.jar = function (store) {
return new RequestJar(store)
}
+79
View File
@@ -0,0 +1,79 @@
'use strict'
function formatHostname (hostname) {
// canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
return hostname.replace(/^\.*/, '.').toLowerCase()
}
function parseNoProxyZone (zone) {
zone = zone.trim().toLowerCase()
var zoneParts = zone.split(':', 2)
var zoneHost = formatHostname(zoneParts[0])
var zonePort = zoneParts[1]
var hasPort = zone.indexOf(':') > -1
return {hostname: zoneHost, port: zonePort, hasPort: hasPort}
}
function uriInNoProxy (uri, noProxy) {
var port = uri.port || (uri.protocol === 'https:' ? '443' : '80')
var hostname = formatHostname(uri.hostname)
var noProxyList = noProxy.split(',')
// iterate through the noProxyList until it finds a match.
return noProxyList.map(parseNoProxyZone).some(function (noProxyZone) {
var isMatchedAt = hostname.indexOf(noProxyZone.hostname)
var hostnameMatched = (
isMatchedAt > -1 &&
(isMatchedAt === hostname.length - noProxyZone.hostname.length)
)
if (noProxyZone.hasPort) {
return (port === noProxyZone.port) && hostnameMatched
}
return hostnameMatched
})
}
function getProxyFromURI (uri) {
// Decide the proper request proxy to use based on the request URI object and the
// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
// respect NO_PROXY environment variables (see: https://lynx.invisible-island.net/lynx2.8.7/breakout/lynx_help/keystrokes/environments.html)
var noProxy = process.env.NO_PROXY || process.env.no_proxy || ''
// if the noProxy is a wildcard then return null
if (noProxy === '*') {
return null
}
// if the noProxy is not empty and the uri is found return null
if (noProxy !== '' && uriInNoProxy(uri, noProxy)) {
return null
}
// Check for HTTP or HTTPS Proxy in environment Else default to null
if (uri.protocol === 'http:') {
return process.env.HTTP_PROXY ||
process.env.http_proxy || null
}
if (uri.protocol === 'https:') {
return process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy || null
}
// if none of that works, return null
// (What uri protocol are you using then?)
return null
}
module.exports = getProxyFromURI
+200
View File
@@ -0,0 +1,200 @@
'use strict'
var fs = require('fs')
var qs = require('querystring')
var extend = require('extend')
function Har (request) {
this.request = request
}
Har.prototype.reducer = function (obj, pair) {
// new property ?
if (obj[pair.name] === undefined) {
obj[pair.name] = pair.value
return obj
}
// existing? convert to array
var arr = [
obj[pair.name],
pair.value
]
obj[pair.name] = arr
return obj
}
Har.prototype.prep = function (data) {
// construct utility properties
data.queryObj = {}
data.headersObj = {}
data.postData.jsonObj = false
data.postData.paramsObj = false
// construct query objects
if (data.queryString && data.queryString.length) {
data.queryObj = data.queryString.reduce(this.reducer, {})
}
// construct headers objects
if (data.headers && data.headers.length) {
// loweCase header keys
data.headersObj = data.headers.reduceRight(function (headers, header) {
headers[header.name] = header.value
return headers
}, {})
}
// construct Cookie header
if (data.cookies && data.cookies.length) {
var cookies = data.cookies.map(function (cookie) {
return cookie.name + '=' + cookie.value
})
if (cookies.length) {
data.headersObj.cookie = cookies.join('; ')
}
}
// prep body
function some (arr) {
return arr.some(function (type) {
return data.postData.mimeType.indexOf(type) === 0
})
}
if (some([
'multipart/mixed',
'multipart/related',
'multipart/form-data',
'multipart/alternative'])) {
// reset values
data.postData.mimeType = 'multipart/form-data'
} else if (some([
'application/x-www-form-urlencoded'])) {
if (!data.postData.params) {
data.postData.text = ''
} else {
data.postData.paramsObj = data.postData.params.reduce(this.reducer, {})
// always overwrite
data.postData.text = qs.stringify(data.postData.paramsObj)
}
} else if (some([
'text/json',
'text/x-json',
'application/json',
'application/x-json'])) {
data.postData.mimeType = 'application/json'
if (data.postData.text) {
try {
data.postData.jsonObj = JSON.parse(data.postData.text)
} catch (e) {
this.request.debug(e)
// force back to text/plain
data.postData.mimeType = 'text/plain'
}
}
}
return data
}
Har.prototype.options = function (options) {
// skip if no har property defined
if (!options.har) {
return options
}
var har = {}
extend(har, options.har)
// only process the first entry
if (har.log && har.log.entries) {
har = har.log.entries[0]
}
// add optional properties to make validation successful
har.url = har.url || options.url || options.uri || options.baseUrl || '/'
har.httpVersion = har.httpVersion || 'HTTP/1.1'
har.queryString = har.queryString || []
har.headers = har.headers || []
har.cookies = har.cookies || []
har.postData = har.postData || {}
har.postData.mimeType = har.postData.mimeType || 'application/octet-stream'
har.bodySize = 0
har.headersSize = 0
har.postData.size = 0
// clean up and get some utility properties
var req = this.prep(har)
// construct new options
if (req.url) {
options.url = req.url
}
if (req.method) {
options.method = req.method
}
if (Object.keys(req.queryObj).length) {
options.qs = req.queryObj
}
if (Object.keys(req.headersObj).length) {
options.headers = req.headersObj
}
function test (type) {
return req.postData.mimeType.indexOf(type) === 0
}
if (test('application/x-www-form-urlencoded')) {
options.form = req.postData.paramsObj
} else if (test('application/json')) {
if (req.postData.jsonObj) {
options.body = req.postData.jsonObj
options.json = true
}
} else if (test('multipart/form-data')) {
options.formData = {}
req.postData.params.forEach(function (param) {
var attachment = {}
if (!param.fileName && !param.contentType) {
options.formData[param.name] = param.value
return
}
// attempt to read from disk!
if (param.fileName && !param.value) {
attachment.value = fs.createReadStream(param.fileName)
} else if (param.value) {
attachment.value = param.value
}
if (param.fileName) {
attachment.options = {
filename: param.fileName,
contentType: param.contentType ? param.contentType : null
}
}
options.formData[param.name] = attachment
})
} else {
if (req.postData.text) {
options.body = req.postData.text
}
}
return options
}
exports.Har = Har
+89
View File
@@ -0,0 +1,89 @@
'use strict'
var crypto = require('crypto')
function randomString (size) {
var bits = (size + 1) * 6
var buffer = crypto.randomBytes(Math.ceil(bits / 8))
var string = buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
return string.slice(0, size)
}
function calculatePayloadHash (payload, algorithm, contentType) {
var hash = crypto.createHash(algorithm)
hash.update('hawk.1.payload\n')
hash.update((contentType ? contentType.split(';')[0].trim().toLowerCase() : '') + '\n')
hash.update(payload || '')
hash.update('\n')
return hash.digest('base64')
}
exports.calculateMac = function (credentials, opts) {
var normalized = 'hawk.1.header\n' +
opts.ts + '\n' +
opts.nonce + '\n' +
(opts.method || '').toUpperCase() + '\n' +
opts.resource + '\n' +
opts.host.toLowerCase() + '\n' +
opts.port + '\n' +
(opts.hash || '') + '\n'
if (opts.ext) {
normalized = normalized + opts.ext.replace('\\', '\\\\').replace('\n', '\\n')
}
normalized = normalized + '\n'
if (opts.app) {
normalized = normalized + opts.app + '\n' + (opts.dlg || '') + '\n'
}
var hmac = crypto.createHmac(credentials.algorithm, credentials.key).update(normalized)
var digest = hmac.digest('base64')
return digest
}
exports.header = function (uri, method, opts) {
var timestamp = opts.timestamp || Math.floor((Date.now() + (opts.localtimeOffsetMsec || 0)) / 1000)
var credentials = opts.credentials
if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
return ''
}
if (['sha1', 'sha256'].indexOf(credentials.algorithm) === -1) {
return ''
}
var artifacts = {
ts: timestamp,
nonce: opts.nonce || randomString(6),
method: method,
resource: uri.pathname + (uri.search || ''),
host: uri.hostname,
port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
hash: opts.hash,
ext: opts.ext,
app: opts.app,
dlg: opts.dlg
}
if (!artifacts.hash && (opts.payload || opts.payload === '')) {
artifacts.hash = calculatePayloadHash(opts.payload, credentials.algorithm, opts.contentType)
}
var mac = exports.calculateMac(credentials, artifacts)
var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''
var header = 'Hawk id="' + credentials.id +
'", ts="' + artifacts.ts +
'", nonce="' + artifacts.nonce +
(artifacts.hash ? '", hash="' + artifacts.hash : '') +
(hasExt ? '", ext="' + artifacts.ext.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : '') +
'", mac="' + mac + '"'
if (artifacts.app) {
header = header + ', app="' + artifacts.app + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"'
}
return header
}
+66
View File
@@ -0,0 +1,66 @@
'use strict'
var jsonSafeStringify = require('json-stringify-safe')
var crypto = require('crypto')
var Buffer = require('safe-buffer').Buffer
var defer = typeof setImmediate === 'undefined'
? process.nextTick
: setImmediate
function paramsHaveRequestBody (params) {
return (
params.body ||
params.requestBodyStream ||
(params.json && typeof params.json !== 'boolean') ||
params.multipart
)
}
function safeStringify (obj, replacer) {
var ret
try {
ret = JSON.stringify(obj, replacer)
} catch (e) {
ret = jsonSafeStringify(obj, replacer)
}
return ret
}
function md5 (str) {
return crypto.createHash('md5').update(str).digest('hex')
}
function isReadStream (rs) {
return rs.readable && rs.path && rs.mode
}
function toBase64 (str) {
return Buffer.from(str || '', 'utf8').toString('base64')
}
function copy (obj) {
var o = {}
Object.keys(obj).forEach(function (i) {
o[i] = obj[i]
})
return o
}
function version () {
var numbers = process.version.replace('v', '').split('.')
return {
major: parseInt(numbers[0], 10),
minor: parseInt(numbers[1], 10),
patch: parseInt(numbers[2], 10)
}
}
exports.paramsHaveRequestBody = paramsHaveRequestBody
exports.safeStringify = safeStringify
exports.md5 = md5
exports.isReadStream = isReadStream
exports.toBase64 = toBase64
exports.copy = copy
exports.version = version
exports.defer = defer
+112
View File
@@ -0,0 +1,112 @@
'use strict'
var { v4: uuid } = require('uuid')
var CombinedStream = require('combined-stream')
var isstream = require('isstream')
var Buffer = require('safe-buffer').Buffer
function Multipart (request) {
this.request = request
this.boundary = uuid()
this.chunked = false
this.body = null
}
Multipart.prototype.isChunked = function (options) {
var self = this
var chunked = false
var parts = options.data || options
if (!parts.forEach) {
self.request.emit('error', new Error('Argument error, options.multipart.'))
}
if (options.chunked !== undefined) {
chunked = options.chunked
}
if (self.request.getHeader('transfer-encoding') === 'chunked') {
chunked = true
}
if (!chunked) {
parts.forEach(function (part) {
if (typeof part.body === 'undefined') {
self.request.emit('error', new Error('Body attribute missing in multipart.'))
}
if (isstream(part.body)) {
chunked = true
}
})
}
return chunked
}
Multipart.prototype.setHeaders = function (chunked) {
var self = this
if (chunked && !self.request.hasHeader('transfer-encoding')) {
self.request.setHeader('transfer-encoding', 'chunked')
}
var header = self.request.getHeader('content-type')
if (!header || header.indexOf('multipart') === -1) {
self.request.setHeader('content-type', 'multipart/related; boundary=' + self.boundary)
} else {
if (header.indexOf('boundary') !== -1) {
self.boundary = header.replace(/.*boundary=([^\s;]+).*/, '$1')
} else {
self.request.setHeader('content-type', header + '; boundary=' + self.boundary)
}
}
}
Multipart.prototype.build = function (parts, chunked) {
var self = this
var body = chunked ? new CombinedStream() : []
function add (part) {
if (typeof part === 'number') {
part = part.toString()
}
return chunked ? body.append(part) : body.push(Buffer.from(part))
}
if (self.request.preambleCRLF) {
add('\r\n')
}
parts.forEach(function (part) {
var preamble = '--' + self.boundary + '\r\n'
Object.keys(part).forEach(function (key) {
if (key === 'body') { return }
preamble += key + ': ' + part[key] + '\r\n'
})
preamble += '\r\n'
add(preamble)
add(part.body)
add('\r\n')
})
add('--' + self.boundary + '--')
if (self.request.postambleCRLF) {
add('\r\n')
}
return body
}
Multipart.prototype.onRequest = function (options) {
var self = this
var chunked = self.isChunked(options)
var parts = options.data || options
self.setHeaders(chunked)
self.chunked = chunked
self.body = self.build(parts, chunked)
}
exports.Multipart = Multipart
+50
View File
@@ -0,0 +1,50 @@
'use strict'
var qs = require('qs')
var querystring = require('querystring')
function Querystring (request) {
this.request = request
this.lib = null
this.useQuerystring = null
this.parseOptions = null
this.stringifyOptions = null
}
Querystring.prototype.init = function (options) {
if (this.lib) { return }
this.useQuerystring = options.useQuerystring
this.lib = (this.useQuerystring ? querystring : qs)
this.parseOptions = options.qsParseOptions || {}
this.stringifyOptions = options.qsStringifyOptions || {}
}
Querystring.prototype.stringify = function (obj) {
return (this.useQuerystring)
? this.rfc3986(this.lib.stringify(obj,
this.stringifyOptions.sep || null,
this.stringifyOptions.eq || null,
this.stringifyOptions))
: this.lib.stringify(obj, this.stringifyOptions)
}
Querystring.prototype.parse = function (str) {
return (this.useQuerystring)
? this.lib.parse(str,
this.parseOptions.sep || null,
this.parseOptions.eq || null,
this.parseOptions)
: this.lib.parse(str, this.parseOptions)
}
Querystring.prototype.rfc3986 = function (str) {
return str.replace(/[!'()*]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
})
}
Querystring.prototype.unescape = querystring.unescape
exports.Querystring = Querystring
+179
View File
@@ -0,0 +1,179 @@
'use strict'
var url = require('url')
var isUrl = /^https?:/
function Redirect (request) {
this.request = request
this.followRedirect = true
this.followRedirects = true
this.followAllRedirects = false
this.followOriginalHttpMethod = false
this.allowRedirect = function () { return true }
this.maxRedirects = 10
this.redirects = []
this.redirectsFollowed = 0
this.removeRefererHeader = false
this.allowInsecureRedirect = false
}
Redirect.prototype.onRequest = function (options) {
var self = this
if (options.maxRedirects !== undefined) {
self.maxRedirects = options.maxRedirects
}
if (typeof options.followRedirect === 'function') {
self.allowRedirect = options.followRedirect
}
if (options.followRedirect !== undefined) {
self.followRedirects = !!options.followRedirect
}
if (options.followAllRedirects !== undefined) {
self.followAllRedirects = options.followAllRedirects
}
if (self.followRedirects || self.followAllRedirects) {
self.redirects = self.redirects || []
}
if (options.removeRefererHeader !== undefined) {
self.removeRefererHeader = options.removeRefererHeader
}
if (options.followOriginalHttpMethod !== undefined) {
self.followOriginalHttpMethod = options.followOriginalHttpMethod
}
if (options.allowInsecureRedirect !== undefined) {
self.allowInsecureRedirect = options.allowInsecureRedirect
}
}
Redirect.prototype.redirectTo = function (response) {
var self = this
var request = self.request
var redirectTo = null
if (response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) {
var location = response.caseless.get('location')
request.debug('redirect', location)
if (self.followAllRedirects) {
redirectTo = location
} else if (self.followRedirects) {
switch (request.method) {
case 'PATCH':
case 'PUT':
case 'POST':
case 'DELETE':
// Do not follow redirects
break
default:
redirectTo = location
break
}
}
} else if (response.statusCode === 401) {
var authHeader = request._auth.onResponse(response)
if (authHeader) {
request.setHeader('authorization', authHeader)
redirectTo = request.uri
}
}
return redirectTo
}
Redirect.prototype.onResponse = function (response, callback) {
var self = this
var request = self.request
var redirectTo = self.redirectTo(response)
if (!redirectTo) return callback(null, false)
function processRedirect (shouldRedirect) {
if (!shouldRedirect) return callback(null, false)
if (typeof shouldRedirect === 'string') {
// overridden redirect url
request.debug('redirect overridden', redirectTo)
redirectTo = shouldRedirect
}
request.debug('redirect to', redirectTo)
// ignore any potential response body. it cannot possibly be useful
// to us at this point.
// response.resume should be defined, but check anyway before calling. Workaround for browserify.
if (response.resume) {
response.resume()
}
if (self.redirectsFollowed >= self.maxRedirects) {
return callback(new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + request.uri.href))
}
self.redirectsFollowed += 1
if (!isUrl.test(redirectTo)) {
redirectTo = url.resolve(request.uri.href, redirectTo)
}
var uriPrev = request.uri
request.uri = url.parse(redirectTo)
// handle the case where we change protocol from https to http or vice versa
if (request.uri.protocol !== uriPrev.protocol && self.allowInsecureRedirect) {
delete request.agent
}
self.redirects.push({ statusCode: response.statusCode, redirectUri: redirectTo })
if (self.followAllRedirects && request.method !== 'HEAD' &&
response.statusCode !== 401 && response.statusCode !== 307) {
request.method = self.followOriginalHttpMethod ? request.method : 'GET'
}
// request.method = 'GET' // Force all redirects to use GET || commented out fixes #215
delete request.src
delete request.req
delete request._started
if (response.statusCode !== 401 && response.statusCode !== 307) {
// Remove parameters from the previous response, unless this is the second request
// for a server that requires digest authentication.
delete request.body
delete request._form
if (request.headers) {
request.removeHeader('host')
request.removeHeader('content-type')
request.removeHeader('content-length')
if (request.uri.hostname !== request.originalHost.split(':')[0]) {
// Remove authorization if changing hostnames (but not if just
// changing ports or protocols). This matches the behavior of curl:
// https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710
request.removeHeader('authorization')
}
}
}
if (!self.removeRefererHeader) {
request.setHeader('referer', uriPrev.href)
}
request.emit('redirect')
request.init()
callback(null, true)
}
// test allowRedirect arity; if has more than one argument,
// assume it's asynchronous via a callback
if (self.allowRedirect.length > 1) {
return self.allowRedirect.call(request, response, function (err, result) {
if (err) return callback(err)
processRedirect(result)
})
}
var allowsRedirect = self.allowRedirect.call(request, response)
if (allowsRedirect && allowsRedirect.then) {
return allowsRedirect.then(processRedirect, callback)
}
// treat as a regular boolean
processRedirect(allowsRedirect)
}
exports.Redirect = Redirect
+175
View File
@@ -0,0 +1,175 @@
'use strict'
var url = require('url')
var tunnel = require('tunnel-agent')
var defaultProxyHeaderWhiteList = [
'accept',
'accept-charset',
'accept-encoding',
'accept-language',
'accept-ranges',
'cache-control',
'content-encoding',
'content-language',
'content-location',
'content-md5',
'content-range',
'content-type',
'connection',
'date',
'expect',
'max-forwards',
'pragma',
'referer',
'te',
'user-agent',
'via'
]
var defaultProxyHeaderExclusiveList = [
'proxy-authorization'
]
function constructProxyHost (uriObject) {
var port = uriObject.port
var protocol = uriObject.protocol
var proxyHost = uriObject.hostname + ':'
if (port) {
proxyHost += port
} else if (protocol === 'https:') {
proxyHost += '443'
} else {
proxyHost += '80'
}
return proxyHost
}
function constructProxyHeaderWhiteList (headers, proxyHeaderWhiteList) {
var whiteList = proxyHeaderWhiteList
.reduce(function (set, header) {
set[header.toLowerCase()] = true
return set
}, {})
return Object.keys(headers)
.filter(function (header) {
return whiteList[header.toLowerCase()]
})
.reduce(function (set, header) {
set[header] = headers[header]
return set
}, {})
}
function constructTunnelOptions (request, proxyHeaders) {
var proxy = request.proxy
var tunnelOptions = {
proxy: {
host: proxy.hostname,
port: +proxy.port,
proxyAuth: proxy.auth,
headers: proxyHeaders
},
headers: request.headers,
ca: request.ca,
cert: request.cert,
key: request.key,
passphrase: request.passphrase,
pfx: request.pfx,
ciphers: request.ciphers,
rejectUnauthorized: request.rejectUnauthorized,
secureOptions: request.secureOptions,
secureProtocol: request.secureProtocol
}
return tunnelOptions
}
function constructTunnelFnName (uri, proxy) {
var uriProtocol = (uri.protocol === 'https:' ? 'https' : 'http')
var proxyProtocol = (proxy.protocol === 'https:' ? 'Https' : 'Http')
return [uriProtocol, proxyProtocol].join('Over')
}
function getTunnelFn (request) {
var uri = request.uri
var proxy = request.proxy
var tunnelFnName = constructTunnelFnName(uri, proxy)
return tunnel[tunnelFnName]
}
function Tunnel (request) {
this.request = request
this.proxyHeaderWhiteList = defaultProxyHeaderWhiteList
this.proxyHeaderExclusiveList = []
if (typeof request.tunnel !== 'undefined') {
this.tunnelOverride = request.tunnel
}
}
Tunnel.prototype.isEnabled = function () {
var self = this
var request = self.request
// Tunnel HTTPS by default. Allow the user to override this setting.
// If self.tunnelOverride is set (the user specified a value), use it.
if (typeof self.tunnelOverride !== 'undefined') {
return self.tunnelOverride
}
// If the destination is HTTPS, tunnel.
if (request.uri.protocol === 'https:') {
return true
}
// Otherwise, do not use tunnel.
return false
}
Tunnel.prototype.setup = function (options) {
var self = this
var request = self.request
options = options || {}
if (typeof request.proxy === 'string') {
request.proxy = url.parse(request.proxy)
}
if (!request.proxy || !request.tunnel) {
return false
}
// Setup Proxy Header Exclusive List and White List
if (options.proxyHeaderWhiteList) {
self.proxyHeaderWhiteList = options.proxyHeaderWhiteList
}
if (options.proxyHeaderExclusiveList) {
self.proxyHeaderExclusiveList = options.proxyHeaderExclusiveList
}
var proxyHeaderExclusiveList = self.proxyHeaderExclusiveList.concat(defaultProxyHeaderExclusiveList)
var proxyHeaderWhiteList = self.proxyHeaderWhiteList.concat(proxyHeaderExclusiveList)
// Setup Proxy Headers and Proxy Headers Host
// Only send the Proxy White Listed Header names
var proxyHeaders = constructProxyHeaderWhiteList(request.headers, proxyHeaderWhiteList)
proxyHeaders.host = constructProxyHost(request.uri)
proxyHeaderExclusiveList.forEach(request.removeHeader, request)
// Set Agent from Tunnel Data
var tunnelFn = getTunnelFn(request)
var tunnelOptions = constructTunnelOptions(request, proxyHeaders)
request.agent = tunnelFn(tunnelOptions)
return true
}
Tunnel.defaultProxyHeaderWhiteList = defaultProxyHeaderWhiteList
Tunnel.defaultProxyHeaderExclusiveList = defaultProxyHeaderExclusiveList
exports.Tunnel = Tunnel
+84
View File
@@ -0,0 +1,84 @@
{
"name": "@cypress/request",
"description": "Cypress's fork of a simplified HTTP request client.",
"keywords": [
"http",
"simple",
"util",
"utility"
],
"version": "3.0.6",
"author": "Mikeal Rogers <mikeal.rogers@gmail.com>",
"repository": {
"type": "git",
"url": "https://github.com/cypress-io/request.git"
},
"bugs": {
"url": "http://github.com/cypress-io/request/issues"
},
"license": "Apache-2.0",
"engines": {
"node": ">= 6"
},
"main": "index.js",
"files": [
"lib/",
"index.js",
"request.js"
],
"dependencies": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~4.0.0",
"http-signature": "~1.4.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"performance-now": "^2.1.0",
"qs": "6.13.0",
"safe-buffer": "^5.1.2",
"tough-cookie": "^5.0.0",
"tunnel-agent": "^0.6.0",
"uuid": "^8.3.2"
},
"scripts": {
"test": "npm run lint && npm run test-ci && npm run test-browser",
"test-ci": "tap --no-check-coverage tests/test-*.js",
"test-cov": "nyc --reporter=lcov tape tests/test-*.js",
"test-browser": "echo 'Skipping browser tests.' || node tests/browser/start.js",
"lint": "standard"
},
"devDependencies": {
"bluebird": "^3.2.1",
"browserify": "^13.0.1",
"browserify-istanbul": "^2.0.0",
"buffer-equal": "^1.0.0",
"codecov": "^3.0.4",
"coveralls": "^3.0.2",
"function-bind": "^1.0.2",
"karma": "^3.0.0",
"karma-browserify": "^5.0.1",
"karma-cli": "^2.0.0",
"karma-coverage": "^2.0.3",
"karma-phantomjs-launcher": "^1.0.0",
"karma-tap": "^4.2.0",
"nyc": "^17.0.0",
"phantomjs-prebuilt": "^2.1.3",
"rimraf": "^2.2.8",
"server-destroy": "^1.0.1",
"standard": "^9.0.0",
"tap": "^15.1.5",
"tape": "^4.6.0"
},
"greenkeeper": {
"ignore": [
"hawk",
"har-validator"
]
}
}
+1548
View File
File diff suppressed because it is too large. Load diff