zhuha 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of zhuha might be problematic. Click here for more details.
- package/06-02.html +81 -0
- package/06-02.js +72 -0
- package/06-03.js +7 -0
- package/06-04.js +7 -0
- package/AnswersLW5.pdf +0 -0
- package/m0603/m0603.js +30 -0
- package/m0603/node_modules/.package-lock.json +16 -0
- package/m0603/node_modules/nodemailer/.gitattributes +6 -0
- package/m0603/node_modules/nodemailer/.prettierrc.js +8 -0
- package/m0603/node_modules/nodemailer/CHANGELOG.md +725 -0
- package/m0603/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
- package/m0603/node_modules/nodemailer/CONTRIBUTING.md +67 -0
- package/m0603/node_modules/nodemailer/LICENSE +16 -0
- package/m0603/node_modules/nodemailer/README.md +97 -0
- package/m0603/node_modules/nodemailer/SECURITY.txt +22 -0
- package/m0603/node_modules/nodemailer/lib/addressparser/index.js +313 -0
- package/m0603/node_modules/nodemailer/lib/base64/index.js +142 -0
- package/m0603/node_modules/nodemailer/lib/dkim/index.js +251 -0
- package/m0603/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/m0603/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/m0603/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/m0603/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/m0603/node_modules/nodemailer/lib/fetch/index.js +274 -0
- package/m0603/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/m0603/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
- package/m0603/node_modules/nodemailer/lib/mailer/index.js +427 -0
- package/m0603/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
- package/m0603/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/m0603/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/m0603/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/m0603/node_modules/nodemailer/lib/nodemailer.js +143 -0
- package/m0603/node_modules/nodemailer/lib/qp/index.js +219 -0
- package/m0603/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/m0603/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
- package/m0603/node_modules/nodemailer/lib/shared/index.js +638 -0
- package/m0603/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/m0603/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/m0603/node_modules/nodemailer/lib/smtp-connection/index.js +1796 -0
- package/m0603/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
- package/m0603/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
- package/m0603/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
- package/m0603/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/m0603/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/m0603/node_modules/nodemailer/lib/well-known/services.json +286 -0
- package/m0603/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
- package/m0603/node_modules/nodemailer/package.json +46 -0
- package/m0603/node_modules/nodemailer/postinstall.js +101 -0
- package/m0603/package-lock.json +31 -0
- package/m0603/package.json +15 -0
- package/package.json +16 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const punycode = require('punycode');
|
4
|
+
const mimeFuncs = require('../mime-funcs');
|
5
|
+
const crypto = require('crypto');
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Returns DKIM signature header line
|
9
|
+
*
|
10
|
+
* @param {Object} headers Parsed headers object from MessageParser
|
11
|
+
* @param {String} bodyHash Base64 encoded hash of the message
|
12
|
+
* @param {Object} options DKIM options
|
13
|
+
* @param {String} options.domainName Domain name to be signed for
|
14
|
+
* @param {String} options.keySelector DKIM key selector to use
|
15
|
+
* @param {String} options.privateKey DKIM private key to use
|
16
|
+
* @return {String} Complete header line
|
17
|
+
*/
|
18
|
+
|
19
|
+
module.exports = (headers, hashAlgo, bodyHash, options) => {
|
20
|
+
options = options || {};
|
21
|
+
|
22
|
+
// all listed fields from RFC4871 #5.5
|
23
|
+
let defaultFieldNames =
|
24
|
+
'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
|
25
|
+
'Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:' +
|
26
|
+
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
27
|
+
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
28
|
+
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
29
|
+
'List-Owner:List-Archive';
|
30
|
+
|
31
|
+
let fieldNames = options.headerFieldNames || defaultFieldNames;
|
32
|
+
|
33
|
+
let canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
34
|
+
let dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
35
|
+
|
36
|
+
let signer, signature;
|
37
|
+
|
38
|
+
canonicalizedHeaderData.headers += 'dkim-signature:' + relaxedHeaderLine(dkimHeader);
|
39
|
+
|
40
|
+
signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
|
41
|
+
signer.update(canonicalizedHeaderData.headers);
|
42
|
+
try {
|
43
|
+
signature = signer.sign(options.privateKey, 'base64');
|
44
|
+
} catch (E) {
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
|
48
|
+
return dkimHeader + signature.replace(/(^.{73}|.{75}(?!\r?\n|\r))/g, '$&\r\n ').trim();
|
49
|
+
};
|
50
|
+
|
51
|
+
module.exports.relaxedHeaders = relaxedHeaders;
|
52
|
+
|
53
|
+
function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyHash) {
|
54
|
+
let dkim = [
|
55
|
+
'v=1',
|
56
|
+
'a=rsa-' + hashAlgo,
|
57
|
+
'c=relaxed/relaxed',
|
58
|
+
'd=' + punycode.toASCII(domainName),
|
59
|
+
'q=dns/txt',
|
60
|
+
's=' + keySelector,
|
61
|
+
'bh=' + bodyHash,
|
62
|
+
'h=' + fieldNames
|
63
|
+
].join('; ');
|
64
|
+
|
65
|
+
return mimeFuncs.foldLines('DKIM-Signature: ' + dkim, 76) + ';\r\n b=';
|
66
|
+
}
|
67
|
+
|
68
|
+
function relaxedHeaders(headers, fieldNames, skipFields) {
|
69
|
+
let includedFields = new Set();
|
70
|
+
let skip = new Set();
|
71
|
+
let headerFields = new Map();
|
72
|
+
|
73
|
+
(skipFields || '')
|
74
|
+
.toLowerCase()
|
75
|
+
.split(':')
|
76
|
+
.forEach(field => {
|
77
|
+
skip.add(field.trim());
|
78
|
+
});
|
79
|
+
|
80
|
+
(fieldNames || '')
|
81
|
+
.toLowerCase()
|
82
|
+
.split(':')
|
83
|
+
.filter(field => !skip.has(field.trim()))
|
84
|
+
.forEach(field => {
|
85
|
+
includedFields.add(field.trim());
|
86
|
+
});
|
87
|
+
|
88
|
+
for (let i = headers.length - 1; i >= 0; i--) {
|
89
|
+
let line = headers[i];
|
90
|
+
// only include the first value from bottom to top
|
91
|
+
if (includedFields.has(line.key) && !headerFields.has(line.key)) {
|
92
|
+
headerFields.set(line.key, relaxedHeaderLine(line.line));
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
let headersList = [];
|
97
|
+
let fields = [];
|
98
|
+
includedFields.forEach(field => {
|
99
|
+
if (headerFields.has(field)) {
|
100
|
+
fields.push(field);
|
101
|
+
headersList.push(field + ':' + headerFields.get(field));
|
102
|
+
}
|
103
|
+
});
|
104
|
+
|
105
|
+
return {
|
106
|
+
headers: headersList.join('\r\n') + '\r\n',
|
107
|
+
fieldNames: fields.join(':')
|
108
|
+
};
|
109
|
+
}
|
110
|
+
|
111
|
+
function relaxedHeaderLine(line) {
|
112
|
+
return line
|
113
|
+
.substr(line.indexOf(':') + 1)
|
114
|
+
.replace(/\r?\n/g, '')
|
115
|
+
.replace(/\s+/g, ' ')
|
116
|
+
.trim();
|
117
|
+
}
|
@@ -0,0 +1,281 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
// module to handle cookies
|
4
|
+
|
5
|
+
const urllib = require('url');
|
6
|
+
|
7
|
+
const SESSION_TIMEOUT = 1800; // 30 min
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Creates a biskviit cookie jar for managing cookie values in memory
|
11
|
+
*
|
12
|
+
* @constructor
|
13
|
+
* @param {Object} [options] Optional options object
|
14
|
+
*/
|
15
|
+
class Cookies {
|
16
|
+
constructor(options) {
|
17
|
+
this.options = options || {};
|
18
|
+
this.cookies = [];
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Stores a cookie string to the cookie storage
|
23
|
+
*
|
24
|
+
* @param {String} cookieStr Value from the 'Set-Cookie:' header
|
25
|
+
* @param {String} url Current URL
|
26
|
+
*/
|
27
|
+
set(cookieStr, url) {
|
28
|
+
let urlparts = urllib.parse(url || '');
|
29
|
+
let cookie = this.parse(cookieStr);
|
30
|
+
let domain;
|
31
|
+
|
32
|
+
if (cookie.domain) {
|
33
|
+
domain = cookie.domain.replace(/^\./, '');
|
34
|
+
|
35
|
+
// do not allow cross origin cookies
|
36
|
+
if (
|
37
|
+
// can't be valid if the requested domain is shorter than current hostname
|
38
|
+
urlparts.hostname.length < domain.length ||
|
39
|
+
// prefix domains with dot to be sure that partial matches are not used
|
40
|
+
('.' + urlparts.hostname).substr(-domain.length + 1) !== '.' + domain
|
41
|
+
) {
|
42
|
+
cookie.domain = urlparts.hostname;
|
43
|
+
}
|
44
|
+
} else {
|
45
|
+
cookie.domain = urlparts.hostname;
|
46
|
+
}
|
47
|
+
|
48
|
+
if (!cookie.path) {
|
49
|
+
cookie.path = this.getPath(urlparts.pathname);
|
50
|
+
}
|
51
|
+
|
52
|
+
// if no expire date, then use sessionTimeout value
|
53
|
+
if (!cookie.expires) {
|
54
|
+
cookie.expires = new Date(Date.now() + (Number(this.options.sessionTimeout || SESSION_TIMEOUT) || SESSION_TIMEOUT) * 1000);
|
55
|
+
}
|
56
|
+
|
57
|
+
return this.add(cookie);
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Returns cookie string for the 'Cookie:' header.
|
62
|
+
*
|
63
|
+
* @param {String} url URL to check for
|
64
|
+
* @returns {String} Cookie header or empty string if no matches were found
|
65
|
+
*/
|
66
|
+
get(url) {
|
67
|
+
return this.list(url)
|
68
|
+
.map(cookie => cookie.name + '=' + cookie.value)
|
69
|
+
.join('; ');
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Lists all valied cookie objects for the specified URL
|
74
|
+
*
|
75
|
+
* @param {String} url URL to check for
|
76
|
+
* @returns {Array} An array of cookie objects
|
77
|
+
*/
|
78
|
+
list(url) {
|
79
|
+
let result = [];
|
80
|
+
let i;
|
81
|
+
let cookie;
|
82
|
+
|
83
|
+
for (i = this.cookies.length - 1; i >= 0; i--) {
|
84
|
+
cookie = this.cookies[i];
|
85
|
+
|
86
|
+
if (this.isExpired(cookie)) {
|
87
|
+
this.cookies.splice(i, i);
|
88
|
+
continue;
|
89
|
+
}
|
90
|
+
|
91
|
+
if (this.match(cookie, url)) {
|
92
|
+
result.unshift(cookie);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
return result;
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Parses cookie string from the 'Set-Cookie:' header
|
101
|
+
*
|
102
|
+
* @param {String} cookieStr String from the 'Set-Cookie:' header
|
103
|
+
* @returns {Object} Cookie object
|
104
|
+
*/
|
105
|
+
parse(cookieStr) {
|
106
|
+
let cookie = {};
|
107
|
+
|
108
|
+
(cookieStr || '')
|
109
|
+
.toString()
|
110
|
+
.split(';')
|
111
|
+
.forEach(cookiePart => {
|
112
|
+
let valueParts = cookiePart.split('=');
|
113
|
+
let key = valueParts.shift().trim().toLowerCase();
|
114
|
+
let value = valueParts.join('=').trim();
|
115
|
+
let domain;
|
116
|
+
|
117
|
+
if (!key) {
|
118
|
+
// skip empty parts
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
|
122
|
+
switch (key) {
|
123
|
+
case 'expires':
|
124
|
+
value = new Date(value);
|
125
|
+
// ignore date if can not parse it
|
126
|
+
if (value.toString() !== 'Invalid Date') {
|
127
|
+
cookie.expires = value;
|
128
|
+
}
|
129
|
+
break;
|
130
|
+
|
131
|
+
case 'path':
|
132
|
+
cookie.path = value;
|
133
|
+
break;
|
134
|
+
|
135
|
+
case 'domain':
|
136
|
+
domain = value.toLowerCase();
|
137
|
+
if (domain.length && domain.charAt(0) !== '.') {
|
138
|
+
domain = '.' + domain; // ensure preceeding dot for user set domains
|
139
|
+
}
|
140
|
+
cookie.domain = domain;
|
141
|
+
break;
|
142
|
+
|
143
|
+
case 'max-age':
|
144
|
+
cookie.expires = new Date(Date.now() + (Number(value) || 0) * 1000);
|
145
|
+
break;
|
146
|
+
|
147
|
+
case 'secure':
|
148
|
+
cookie.secure = true;
|
149
|
+
break;
|
150
|
+
|
151
|
+
case 'httponly':
|
152
|
+
cookie.httponly = true;
|
153
|
+
break;
|
154
|
+
|
155
|
+
default:
|
156
|
+
if (!cookie.name) {
|
157
|
+
cookie.name = key;
|
158
|
+
cookie.value = value;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
});
|
162
|
+
|
163
|
+
return cookie;
|
164
|
+
}
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Checks if a cookie object is valid for a specified URL
|
168
|
+
*
|
169
|
+
* @param {Object} cookie Cookie object
|
170
|
+
* @param {String} url URL to check for
|
171
|
+
* @returns {Boolean} true if cookie is valid for specifiec URL
|
172
|
+
*/
|
173
|
+
match(cookie, url) {
|
174
|
+
let urlparts = urllib.parse(url || '');
|
175
|
+
|
176
|
+
// check if hostname matches
|
177
|
+
// .foo.com also matches subdomains, foo.com does not
|
178
|
+
if (
|
179
|
+
urlparts.hostname !== cookie.domain &&
|
180
|
+
(cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)
|
181
|
+
) {
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
|
185
|
+
// check if path matches
|
186
|
+
let path = this.getPath(urlparts.pathname);
|
187
|
+
if (path.substr(0, cookie.path.length) !== cookie.path) {
|
188
|
+
return false;
|
189
|
+
}
|
190
|
+
|
191
|
+
// check secure argument
|
192
|
+
if (cookie.secure && urlparts.protocol !== 'https:') {
|
193
|
+
return false;
|
194
|
+
}
|
195
|
+
|
196
|
+
return true;
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Adds (or updates/removes if needed) a cookie object to the cookie storage
|
201
|
+
*
|
202
|
+
* @param {Object} cookie Cookie value to be stored
|
203
|
+
*/
|
204
|
+
add(cookie) {
|
205
|
+
let i;
|
206
|
+
let len;
|
207
|
+
|
208
|
+
// nothing to do here
|
209
|
+
if (!cookie || !cookie.name) {
|
210
|
+
return false;
|
211
|
+
}
|
212
|
+
|
213
|
+
// overwrite if has same params
|
214
|
+
for (i = 0, len = this.cookies.length; i < len; i++) {
|
215
|
+
if (this.compare(this.cookies[i], cookie)) {
|
216
|
+
// check if the cookie needs to be removed instead
|
217
|
+
if (this.isExpired(cookie)) {
|
218
|
+
this.cookies.splice(i, 1); // remove expired/unset cookie
|
219
|
+
return false;
|
220
|
+
}
|
221
|
+
|
222
|
+
this.cookies[i] = cookie;
|
223
|
+
return true;
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
// add as new if not already expired
|
228
|
+
if (!this.isExpired(cookie)) {
|
229
|
+
this.cookies.push(cookie);
|
230
|
+
}
|
231
|
+
|
232
|
+
return true;
|
233
|
+
}
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Checks if two cookie objects are the same
|
237
|
+
*
|
238
|
+
* @param {Object} a Cookie to check against
|
239
|
+
* @param {Object} b Cookie to check against
|
240
|
+
* @returns {Boolean} True, if the cookies are the same
|
241
|
+
*/
|
242
|
+
compare(a, b) {
|
243
|
+
return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === a.httponly;
|
244
|
+
}
|
245
|
+
|
246
|
+
/**
|
247
|
+
* Checks if a cookie is expired
|
248
|
+
*
|
249
|
+
* @param {Object} cookie Cookie object to check against
|
250
|
+
* @returns {Boolean} True, if the cookie is expired
|
251
|
+
*/
|
252
|
+
isExpired(cookie) {
|
253
|
+
return (cookie.expires && cookie.expires < new Date()) || !cookie.value;
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Returns normalized cookie path for an URL path argument
|
258
|
+
*
|
259
|
+
* @param {String} pathname
|
260
|
+
* @returns {String} Normalized path
|
261
|
+
*/
|
262
|
+
getPath(pathname) {
|
263
|
+
let path = (pathname || '/').split('/');
|
264
|
+
path.pop(); // remove filename part
|
265
|
+
path = path.join('/').trim();
|
266
|
+
|
267
|
+
// ensure path prefix /
|
268
|
+
if (path.charAt(0) !== '/') {
|
269
|
+
path = '/' + path;
|
270
|
+
}
|
271
|
+
|
272
|
+
// ensure path suffix /
|
273
|
+
if (path.substr(-1) !== '/') {
|
274
|
+
path += '/';
|
275
|
+
}
|
276
|
+
|
277
|
+
return path;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
module.exports = Cookies;
|
@@ -0,0 +1,274 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const http = require('http');
|
4
|
+
const https = require('https');
|
5
|
+
const urllib = require('url');
|
6
|
+
const zlib = require('zlib');
|
7
|
+
const PassThrough = require('stream').PassThrough;
|
8
|
+
const Cookies = require('./cookies');
|
9
|
+
const packageData = require('../../package.json');
|
10
|
+
const net = require('net');
|
11
|
+
|
12
|
+
const MAX_REDIRECTS = 5;
|
13
|
+
|
14
|
+
module.exports = function (url, options) {
|
15
|
+
return nmfetch(url, options);
|
16
|
+
};
|
17
|
+
|
18
|
+
module.exports.Cookies = Cookies;
|
19
|
+
|
20
|
+
function nmfetch(url, options) {
|
21
|
+
options = options || {};
|
22
|
+
|
23
|
+
options.fetchRes = options.fetchRes || new PassThrough();
|
24
|
+
options.cookies = options.cookies || new Cookies();
|
25
|
+
options.redirects = options.redirects || 0;
|
26
|
+
options.maxRedirects = isNaN(options.maxRedirects) ? MAX_REDIRECTS : options.maxRedirects;
|
27
|
+
|
28
|
+
if (options.cookie) {
|
29
|
+
[].concat(options.cookie || []).forEach(cookie => {
|
30
|
+
options.cookies.set(cookie, url);
|
31
|
+
});
|
32
|
+
options.cookie = false;
|
33
|
+
}
|
34
|
+
|
35
|
+
let fetchRes = options.fetchRes;
|
36
|
+
let parsed = urllib.parse(url);
|
37
|
+
let method = (options.method || '').toString().trim().toUpperCase() || 'GET';
|
38
|
+
let finished = false;
|
39
|
+
let cookies;
|
40
|
+
let body;
|
41
|
+
|
42
|
+
let handler = parsed.protocol === 'https:' ? https : http;
|
43
|
+
|
44
|
+
let headers = {
|
45
|
+
'accept-encoding': 'gzip,deflate',
|
46
|
+
'user-agent': 'nodemailer/' + packageData.version
|
47
|
+
};
|
48
|
+
|
49
|
+
Object.keys(options.headers || {}).forEach(key => {
|
50
|
+
headers[key.toLowerCase().trim()] = options.headers[key];
|
51
|
+
});
|
52
|
+
|
53
|
+
if (options.userAgent) {
|
54
|
+
headers['user-agent'] = options.userAgent;
|
55
|
+
}
|
56
|
+
|
57
|
+
if (parsed.auth) {
|
58
|
+
headers.Authorization = 'Basic ' + Buffer.from(parsed.auth).toString('base64');
|
59
|
+
}
|
60
|
+
|
61
|
+
if ((cookies = options.cookies.get(url))) {
|
62
|
+
headers.cookie = cookies;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (options.body) {
|
66
|
+
if (options.contentType !== false) {
|
67
|
+
headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
|
68
|
+
}
|
69
|
+
|
70
|
+
if (typeof options.body.pipe === 'function') {
|
71
|
+
// it's a stream
|
72
|
+
headers['Transfer-Encoding'] = 'chunked';
|
73
|
+
body = options.body;
|
74
|
+
body.on('error', err => {
|
75
|
+
if (finished) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
finished = true;
|
79
|
+
err.type = 'FETCH';
|
80
|
+
err.sourceUrl = url;
|
81
|
+
fetchRes.emit('error', err);
|
82
|
+
});
|
83
|
+
} else {
|
84
|
+
if (options.body instanceof Buffer) {
|
85
|
+
body = options.body;
|
86
|
+
} else if (typeof options.body === 'object') {
|
87
|
+
try {
|
88
|
+
// encodeURIComponent can fail on invalid input (partial emoji etc.)
|
89
|
+
body = Buffer.from(
|
90
|
+
Object.keys(options.body)
|
91
|
+
.map(key => {
|
92
|
+
let value = options.body[key].toString().trim();
|
93
|
+
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
94
|
+
})
|
95
|
+
.join('&')
|
96
|
+
);
|
97
|
+
} catch (E) {
|
98
|
+
if (finished) {
|
99
|
+
return;
|
100
|
+
}
|
101
|
+
finished = true;
|
102
|
+
E.type = 'FETCH';
|
103
|
+
E.sourceUrl = url;
|
104
|
+
fetchRes.emit('error', E);
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
} else {
|
108
|
+
body = Buffer.from(options.body.toString().trim());
|
109
|
+
}
|
110
|
+
|
111
|
+
headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
|
112
|
+
headers['Content-Length'] = body.length;
|
113
|
+
}
|
114
|
+
// if method is not provided, use POST instead of GET
|
115
|
+
method = (options.method || '').toString().trim().toUpperCase() || 'POST';
|
116
|
+
}
|
117
|
+
|
118
|
+
let req;
|
119
|
+
let reqOptions = {
|
120
|
+
method,
|
121
|
+
host: parsed.hostname,
|
122
|
+
path: parsed.path,
|
123
|
+
port: parsed.port ? parsed.port : parsed.protocol === 'https:' ? 443 : 80,
|
124
|
+
headers,
|
125
|
+
rejectUnauthorized: false,
|
126
|
+
agent: false
|
127
|
+
};
|
128
|
+
|
129
|
+
if (options.tls) {
|
130
|
+
Object.keys(options.tls).forEach(key => {
|
131
|
+
reqOptions[key] = options.tls[key];
|
132
|
+
});
|
133
|
+
}
|
134
|
+
|
135
|
+
if (parsed.protocol === 'https:' && parsed.hostname && parsed.hostname !== reqOptions.host && !net.isIP(parsed.hostname) && !reqOptions.servername) {
|
136
|
+
reqOptions.servername = parsed.hostname;
|
137
|
+
}
|
138
|
+
|
139
|
+
try {
|
140
|
+
req = handler.request(reqOptions);
|
141
|
+
} catch (E) {
|
142
|
+
finished = true;
|
143
|
+
setImmediate(() => {
|
144
|
+
E.type = 'FETCH';
|
145
|
+
E.sourceUrl = url;
|
146
|
+
fetchRes.emit('error', E);
|
147
|
+
});
|
148
|
+
return fetchRes;
|
149
|
+
}
|
150
|
+
|
151
|
+
if (options.timeout) {
|
152
|
+
req.setTimeout(options.timeout, () => {
|
153
|
+
if (finished) {
|
154
|
+
return;
|
155
|
+
}
|
156
|
+
finished = true;
|
157
|
+
req.abort();
|
158
|
+
let err = new Error('Request Timeout');
|
159
|
+
err.type = 'FETCH';
|
160
|
+
err.sourceUrl = url;
|
161
|
+
fetchRes.emit('error', err);
|
162
|
+
});
|
163
|
+
}
|
164
|
+
|
165
|
+
req.on('error', err => {
|
166
|
+
if (finished) {
|
167
|
+
return;
|
168
|
+
}
|
169
|
+
finished = true;
|
170
|
+
err.type = 'FETCH';
|
171
|
+
err.sourceUrl = url;
|
172
|
+
fetchRes.emit('error', err);
|
173
|
+
});
|
174
|
+
|
175
|
+
req.on('response', res => {
|
176
|
+
let inflate;
|
177
|
+
|
178
|
+
if (finished) {
|
179
|
+
return;
|
180
|
+
}
|
181
|
+
|
182
|
+
switch (res.headers['content-encoding']) {
|
183
|
+
case 'gzip':
|
184
|
+
case 'deflate':
|
185
|
+
inflate = zlib.createUnzip();
|
186
|
+
break;
|
187
|
+
}
|
188
|
+
|
189
|
+
if (res.headers['set-cookie']) {
|
190
|
+
[].concat(res.headers['set-cookie'] || []).forEach(cookie => {
|
191
|
+
options.cookies.set(cookie, url);
|
192
|
+
});
|
193
|
+
}
|
194
|
+
|
195
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
196
|
+
// redirect
|
197
|
+
options.redirects++;
|
198
|
+
if (options.redirects > options.maxRedirects) {
|
199
|
+
finished = true;
|
200
|
+
let err = new Error('Maximum redirect count exceeded');
|
201
|
+
err.type = 'FETCH';
|
202
|
+
err.sourceUrl = url;
|
203
|
+
fetchRes.emit('error', err);
|
204
|
+
req.abort();
|
205
|
+
return;
|
206
|
+
}
|
207
|
+
// redirect does not include POST body
|
208
|
+
options.method = 'GET';
|
209
|
+
options.body = false;
|
210
|
+
return nmfetch(urllib.resolve(url, res.headers.location), options);
|
211
|
+
}
|
212
|
+
|
213
|
+
fetchRes.statusCode = res.statusCode;
|
214
|
+
fetchRes.headers = res.headers;
|
215
|
+
|
216
|
+
if (res.statusCode >= 300 && !options.allowErrorResponse) {
|
217
|
+
finished = true;
|
218
|
+
let err = new Error('Invalid status code ' + res.statusCode);
|
219
|
+
err.type = 'FETCH';
|
220
|
+
err.sourceUrl = url;
|
221
|
+
fetchRes.emit('error', err);
|
222
|
+
req.abort();
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
|
226
|
+
res.on('error', err => {
|
227
|
+
if (finished) {
|
228
|
+
return;
|
229
|
+
}
|
230
|
+
finished = true;
|
231
|
+
err.type = 'FETCH';
|
232
|
+
err.sourceUrl = url;
|
233
|
+
fetchRes.emit('error', err);
|
234
|
+
req.abort();
|
235
|
+
});
|
236
|
+
|
237
|
+
if (inflate) {
|
238
|
+
res.pipe(inflate).pipe(fetchRes);
|
239
|
+
inflate.on('error', err => {
|
240
|
+
if (finished) {
|
241
|
+
return;
|
242
|
+
}
|
243
|
+
finished = true;
|
244
|
+
err.type = 'FETCH';
|
245
|
+
err.sourceUrl = url;
|
246
|
+
fetchRes.emit('error', err);
|
247
|
+
req.abort();
|
248
|
+
});
|
249
|
+
} else {
|
250
|
+
res.pipe(fetchRes);
|
251
|
+
}
|
252
|
+
});
|
253
|
+
|
254
|
+
setImmediate(() => {
|
255
|
+
if (body) {
|
256
|
+
try {
|
257
|
+
if (typeof body.pipe === 'function') {
|
258
|
+
return body.pipe(req);
|
259
|
+
} else {
|
260
|
+
req.write(body);
|
261
|
+
}
|
262
|
+
} catch (err) {
|
263
|
+
finished = true;
|
264
|
+
err.type = 'FETCH';
|
265
|
+
err.sourceUrl = url;
|
266
|
+
fetchRes.emit('error', err);
|
267
|
+
return;
|
268
|
+
}
|
269
|
+
}
|
270
|
+
req.end();
|
271
|
+
});
|
272
|
+
|
273
|
+
return fetchRes;
|
274
|
+
}
|