index.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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. body = Buffer.from(
  75. Object.keys(options.body)
  76. .map(key => {
  77. let value = options.body[key].toString().trim();
  78. return encodeURIComponent(key) + '=' + encodeURIComponent(value);
  79. })
  80. .join('&')
  81. );
  82. } else {
  83. body = Buffer.from(options.body.toString().trim());
  84. }
  85. headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
  86. headers['Content-Length'] = body.length;
  87. }
  88. // if method is not provided, use POST instead of GET
  89. method =
  90. (options.method || '')
  91. .toString()
  92. .trim()
  93. .toUpperCase() || 'POST';
  94. }
  95. let req;
  96. let reqOptions = {
  97. method,
  98. host: parsed.hostname,
  99. path: parsed.path,
  100. port: parsed.port ? parsed.port : parsed.protocol === 'https:' ? 443 : 80,
  101. headers,
  102. rejectUnauthorized: false,
  103. agent: false
  104. };
  105. if (options.tls) {
  106. Object.keys(options.tls).forEach(key => {
  107. reqOptions[key] = options.tls[key];
  108. });
  109. }
  110. try {
  111. req = handler.request(reqOptions);
  112. } catch (E) {
  113. finished = true;
  114. setImmediate(() => {
  115. E.type = 'FETCH';
  116. E.sourceUrl = url;
  117. fetchRes.emit('error', E);
  118. });
  119. return fetchRes;
  120. }
  121. if (options.timeout) {
  122. req.setTimeout(options.timeout, () => {
  123. if (finished) {
  124. return;
  125. }
  126. finished = true;
  127. req.abort();
  128. let err = new Error('Request Timeout');
  129. err.type = 'FETCH';
  130. err.sourceUrl = url;
  131. fetchRes.emit('error', err);
  132. });
  133. }
  134. req.on('error', err => {
  135. if (finished) {
  136. return;
  137. }
  138. finished = true;
  139. err.type = 'FETCH';
  140. err.sourceUrl = url;
  141. fetchRes.emit('error', err);
  142. });
  143. req.on('response', res => {
  144. let inflate;
  145. if (finished) {
  146. return;
  147. }
  148. switch (res.headers['content-encoding']) {
  149. case 'gzip':
  150. case 'deflate':
  151. inflate = zlib.createUnzip();
  152. break;
  153. }
  154. if (res.headers['set-cookie']) {
  155. [].concat(res.headers['set-cookie'] || []).forEach(cookie => {
  156. options.cookies.set(cookie, url);
  157. });
  158. }
  159. if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
  160. // redirect
  161. options.redirects++;
  162. if (options.redirects > options.maxRedirects) {
  163. finished = true;
  164. let err = new Error('Maximum redirect count exceeded');
  165. err.type = 'FETCH';
  166. err.sourceUrl = url;
  167. fetchRes.emit('error', err);
  168. req.abort();
  169. return;
  170. }
  171. return fetch(urllib.resolve(url, res.headers.location), options);
  172. }
  173. fetchRes.statusCode = res.statusCode;
  174. if (res.statusCode >= 300 && !options.allowErrorResponse) {
  175. finished = true;
  176. let err = new Error('Invalid status code ' + res.statusCode);
  177. err.type = 'FETCH';
  178. err.sourceUrl = url;
  179. fetchRes.emit('error', err);
  180. req.abort();
  181. return;
  182. }
  183. res.on('error', err => {
  184. if (finished) {
  185. return;
  186. }
  187. finished = true;
  188. err.type = 'FETCH';
  189. err.sourceUrl = url;
  190. fetchRes.emit('error', err);
  191. req.abort();
  192. });
  193. if (inflate) {
  194. res.pipe(inflate).pipe(fetchRes);
  195. inflate.on('error', err => {
  196. if (finished) {
  197. return;
  198. }
  199. finished = true;
  200. err.type = 'FETCH';
  201. err.sourceUrl = url;
  202. fetchRes.emit('error', err);
  203. req.abort();
  204. });
  205. } else {
  206. res.pipe(fetchRes);
  207. }
  208. });
  209. setImmediate(() => {
  210. if (body) {
  211. try {
  212. if (typeof body.pipe === 'function') {
  213. return body.pipe(req);
  214. } else {
  215. req.write(body);
  216. }
  217. } catch (err) {
  218. finished = true;
  219. err.type = 'FETCH';
  220. err.sourceUrl = url;
  221. fetchRes.emit('error', err);
  222. return;
  223. }
  224. }
  225. req.end();
  226. });
  227. return fetchRes;
  228. }