index.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. 'use strict';
  2. const http = require('http');
  3. const https = require('https');
  4. const urllib = require('url');
  5. const zlib = require('zlib');
  6. const PassThrough = require('stream').PassThrough;
  7. const Cookies = require('./cookies');
  8. const packageData = require('../../package.json');
  9. const MAX_REDIRECTS = 5;
  10. module.exports = function(url, options) {
  11. return fetch(url, options);
  12. };
  13. module.exports.Cookies = Cookies;
  14. function fetch(url, options) {
  15. options = options || {};
  16. options.fetchRes = options.fetchRes || new PassThrough();
  17. options.cookies = options.cookies || new Cookies();
  18. options.redirects = options.redirects || 0;
  19. options.maxRedirects = isNaN(options.maxRedirects) ? MAX_REDIRECTS : options.maxRedirects;
  20. if (options.cookie) {
  21. [].concat(options.cookie || []).forEach(cookie => {
  22. options.cookies.set(cookie, url);
  23. });
  24. options.cookie = false;
  25. }
  26. let fetchRes = options.fetchRes;
  27. let parsed = urllib.parse(url);
  28. let method =
  29. (options.method || '')
  30. .toString()
  31. .trim()
  32. .toUpperCase() || 'GET';
  33. let finished = false;
  34. let cookies;
  35. let body;
  36. let handler = parsed.protocol === 'https:' ? https : http;
  37. let headers = {
  38. 'accept-encoding': 'gzip,deflate',
  39. 'user-agent': 'nodemailer/' + packageData.version
  40. };
  41. Object.keys(options.headers || {}).forEach(key => {
  42. headers[key.toLowerCase().trim()] = options.headers[key];
  43. });
  44. if (options.userAgent) {
  45. headers['user-agent'] = options.userAgent;
  46. }
  47. if (parsed.auth) {
  48. headers.Authorization = 'Basic ' + Buffer.from(parsed.auth).toString('base64');
  49. }
  50. if ((cookies = options.cookies.get(url))) {
  51. headers.cookie = cookies;
  52. }
  53. if (options.body) {
  54. if (options.contentType !== false) {
  55. headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
  56. }
  57. if (typeof options.body.pipe === 'function') {
  58. // it's a stream
  59. headers['Transfer-Encoding'] = 'chunked';
  60. body = options.body;
  61. body.on('error', err => {
  62. if (finished) {
  63. return;
  64. }
  65. finished = true;
  66. err.type = 'FETCH';
  67. err.sourceUrl = url;
  68. fetchRes.emit('error', err);
  69. });
  70. } else {
  71. if (options.body instanceof Buffer) {
  72. body = options.body;
  73. } else if (typeof options.body === 'object') {
  74. try {
  75. // encodeURIComponent can fail on invalid input (partial emoji etc.)
  76. body = Buffer.from(
  77. Object.keys(options.body)
  78. .map(key => {
  79. let value = options.body[key].toString().trim();
  80. return encodeURIComponent(key) + '=' + encodeURIComponent(value);
  81. })
  82. .join('&')
  83. );
  84. } catch (E) {
  85. if (finished) {
  86. return;
  87. }
  88. finished = true;
  89. E.type = 'FETCH';
  90. E.sourceUrl = url;
  91. fetchRes.emit('error', E);
  92. return;
  93. }
  94. } else {
  95. body = Buffer.from(options.body.toString().trim());
  96. }
  97. headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
  98. headers['Content-Length'] = body.length;
  99. }
  100. // if method is not provided, use POST instead of GET
  101. method =
  102. (options.method || '')
  103. .toString()
  104. .trim()
  105. .toUpperCase() || 'POST';
  106. }
  107. let req;
  108. let reqOptions = {
  109. method,
  110. host: parsed.hostname,
  111. path: parsed.path,
  112. port: parsed.port ? parsed.port : parsed.protocol === 'https:' ? 443 : 80,
  113. headers,
  114. rejectUnauthorized: false,
  115. agent: false
  116. };
  117. if (options.tls) {
  118. Object.keys(options.tls).forEach(key => {
  119. reqOptions[key] = options.tls[key];
  120. });
  121. }
  122. try {
  123. req = handler.request(reqOptions);
  124. } catch (E) {
  125. finished = true;
  126. setImmediate(() => {
  127. E.type = 'FETCH';
  128. E.sourceUrl = url;
  129. fetchRes.emit('error', E);
  130. });
  131. return fetchRes;
  132. }
  133. if (options.timeout) {
  134. req.setTimeout(options.timeout, () => {
  135. if (finished) {
  136. return;
  137. }
  138. finished = true;
  139. req.abort();
  140. let err = new Error('Request Timeout');
  141. err.type = 'FETCH';
  142. err.sourceUrl = url;
  143. fetchRes.emit('error', err);
  144. });
  145. }
  146. req.on('error', err => {
  147. if (finished) {
  148. return;
  149. }
  150. finished = true;
  151. err.type = 'FETCH';
  152. err.sourceUrl = url;
  153. fetchRes.emit('error', err);
  154. });
  155. req.on('response', res => {
  156. let inflate;
  157. if (finished) {
  158. return;
  159. }
  160. switch (res.headers['content-encoding']) {
  161. case 'gzip':
  162. case 'deflate':
  163. inflate = zlib.createUnzip();
  164. break;
  165. }
  166. if (res.headers['set-cookie']) {
  167. [].concat(res.headers['set-cookie'] || []).forEach(cookie => {
  168. options.cookies.set(cookie, url);
  169. });
  170. }
  171. if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
  172. // redirect
  173. options.redirects++;
  174. if (options.redirects > options.maxRedirects) {
  175. finished = true;
  176. let err = new Error('Maximum redirect count exceeded');
  177. err.type = 'FETCH';
  178. err.sourceUrl = url;
  179. fetchRes.emit('error', err);
  180. req.abort();
  181. return;
  182. }
  183. // redirect does not include POST body
  184. options.method = 'GET';
  185. options.body = false;
  186. return fetch(urllib.resolve(url, res.headers.location), options);
  187. }
  188. fetchRes.statusCode = res.statusCode;
  189. fetchRes.headers = res.headers;
  190. if (res.statusCode >= 300 && !options.allowErrorResponse) {
  191. finished = true;
  192. let err = new Error('Invalid status code ' + res.statusCode);
  193. err.type = 'FETCH';
  194. err.sourceUrl = url;
  195. fetchRes.emit('error', err);
  196. req.abort();
  197. return;
  198. }
  199. res.on('error', err => {
  200. if (finished) {
  201. return;
  202. }
  203. finished = true;
  204. err.type = 'FETCH';
  205. err.sourceUrl = url;
  206. fetchRes.emit('error', err);
  207. req.abort();
  208. });
  209. if (inflate) {
  210. res.pipe(inflate).pipe(fetchRes);
  211. inflate.on('error', err => {
  212. if (finished) {
  213. return;
  214. }
  215. finished = true;
  216. err.type = 'FETCH';
  217. err.sourceUrl = url;
  218. fetchRes.emit('error', err);
  219. req.abort();
  220. });
  221. } else {
  222. res.pipe(fetchRes);
  223. }
  224. });
  225. setImmediate(() => {
  226. if (body) {
  227. try {
  228. if (typeof body.pipe === 'function') {
  229. return body.pipe(req);
  230. } else {
  231. req.write(body);
  232. }
  233. } catch (err) {
  234. finished = true;
  235. err.type = 'FETCH';
  236. err.sourceUrl = url;
  237. fetchRes.emit('error', err);
  238. return;
  239. }
  240. }
  241. req.end();
  242. });
  243. return fetchRes;
  244. }