polikolog 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. package/.idea/5lab.iml +12 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  3. package/.idea/jsLibraryMappings.xml +6 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/06-02.js +48 -0
  7. package/06-03.js +22 -0
  8. package/06-04.js +22 -0
  9. package/index.html +41 -0
  10. package/m0603.js +28 -0
  11. package/mypackage/m0603.js +28 -0
  12. package/mypackage/node_modules/.package-lock.json +24 -0
  13. package/mypackage/node_modules/nodemailer/.gitattributes +6 -0
  14. package/mypackage/node_modules/nodemailer/.prettierrc.js +8 -0
  15. package/mypackage/node_modules/nodemailer/CHANGELOG.md +725 -0
  16. package/mypackage/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
  17. package/mypackage/node_modules/nodemailer/CONTRIBUTING.md +67 -0
  18. package/mypackage/node_modules/nodemailer/LICENSE +16 -0
  19. package/mypackage/node_modules/nodemailer/README.md +97 -0
  20. package/mypackage/node_modules/nodemailer/SECURITY.txt +22 -0
  21. package/mypackage/node_modules/nodemailer/lib/addressparser/index.js +313 -0
  22. package/mypackage/node_modules/nodemailer/lib/base64/index.js +142 -0
  23. package/mypackage/node_modules/nodemailer/lib/dkim/index.js +251 -0
  24. package/mypackage/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
  25. package/mypackage/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
  26. package/mypackage/node_modules/nodemailer/lib/dkim/sign.js +117 -0
  27. package/mypackage/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
  28. package/mypackage/node_modules/nodemailer/lib/fetch/index.js +274 -0
  29. package/mypackage/node_modules/nodemailer/lib/json-transport/index.js +82 -0
  30. package/mypackage/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
  31. package/mypackage/node_modules/nodemailer/lib/mailer/index.js +427 -0
  32. package/mypackage/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
  33. package/mypackage/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
  34. package/mypackage/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
  35. package/mypackage/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
  36. package/mypackage/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
  37. package/mypackage/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
  38. package/mypackage/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
  39. package/mypackage/node_modules/nodemailer/lib/nodemailer.js +143 -0
  40. package/mypackage/node_modules/nodemailer/lib/qp/index.js +219 -0
  41. package/mypackage/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
  42. package/mypackage/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
  43. package/mypackage/node_modules/nodemailer/lib/shared/index.js +638 -0
  44. package/mypackage/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
  45. package/mypackage/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
  46. package/mypackage/node_modules/nodemailer/lib/smtp-connection/index.js +1796 -0
  47. package/mypackage/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
  48. package/mypackage/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
  49. package/mypackage/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
  50. package/mypackage/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
  51. package/mypackage/node_modules/nodemailer/lib/well-known/index.js +47 -0
  52. package/mypackage/node_modules/nodemailer/lib/well-known/services.json +286 -0
  53. package/mypackage/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
  54. package/mypackage/node_modules/nodemailer/package.json +46 -0
  55. package/mypackage/node_modules/nodemailer/postinstall.js +101 -0
  56. package/mypackage/package.json +15 -0
  57. package/package.json +15 -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
+ }