source: node_modules/nodemailer/lib/fetch/index.js

main
Last change on this file was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 8.0 KB
Line 
1'use strict';
2
3const http = require('http');
4const https = require('https');
5const urllib = require('url');
6const zlib = require('zlib');
7const PassThrough = require('stream').PassThrough;
8const Cookies = require('./cookies');
9const packageData = require('../../package.json');
10const net = require('net');
11
12const MAX_REDIRECTS = 5;
13
14module.exports = function (url, options) {
15 return nmfetch(url, options);
16};
17
18module.exports.Cookies = Cookies;
19
20function 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}
Note: See TracBrowser for help on using the repository browser.