fetcher.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.DefaultFetcher = exports.BaseFetcher = void 0;
  7. const debug_1 = __importDefault(require("debug"));
  8. const fs_1 = __importDefault(require("fs"));
  9. const make_fetch_happen_1 = __importDefault(require("make-fetch-happen"));
  10. const util_1 = __importDefault(require("util"));
  11. const error_1 = require("./error");
  12. const tmpfile_1 = require("./utils/tmpfile");
  13. const log = (0, debug_1.default)('tuf:fetch');
  14. class BaseFetcher {
  15. // Download file from given URL. The file is downloaded to a temporary
  16. // location and then passed to the given handler. The handler is responsible
  17. // for moving the file to its final location. The temporary file is deleted
  18. // after the handler returns.
  19. async downloadFile(url, maxLength, handler) {
  20. return (0, tmpfile_1.withTempFile)(async (tmpFile) => {
  21. const reader = await this.fetch(url);
  22. let numberOfBytesReceived = 0;
  23. const fileStream = fs_1.default.createWriteStream(tmpFile);
  24. // Read the stream a chunk at a time so that we can check
  25. // the length of the file as we go
  26. try {
  27. for await (const chunk of reader) {
  28. const bufferChunk = Buffer.from(chunk);
  29. numberOfBytesReceived += bufferChunk.length;
  30. if (numberOfBytesReceived > maxLength) {
  31. throw new error_1.DownloadLengthMismatchError('Max length reached');
  32. }
  33. await writeBufferToStream(fileStream, bufferChunk);
  34. }
  35. }
  36. finally {
  37. // Make sure we always close the stream
  38. await util_1.default.promisify(fileStream.close).bind(fileStream)();
  39. }
  40. return handler(tmpFile);
  41. });
  42. }
  43. // Download bytes from given URL.
  44. async downloadBytes(url, maxLength) {
  45. return this.downloadFile(url, maxLength, async (file) => {
  46. const stream = fs_1.default.createReadStream(file);
  47. const chunks = [];
  48. for await (const chunk of stream) {
  49. chunks.push(chunk);
  50. }
  51. return Buffer.concat(chunks);
  52. });
  53. }
  54. }
  55. exports.BaseFetcher = BaseFetcher;
  56. class DefaultFetcher extends BaseFetcher {
  57. constructor(options = {}) {
  58. super();
  59. this.timeout = options.timeout;
  60. this.retry = options.retry;
  61. }
  62. async fetch(url) {
  63. log('GET %s', url);
  64. const response = await (0, make_fetch_happen_1.default)(url, {
  65. timeout: this.timeout,
  66. retry: this.retry,
  67. });
  68. if (!response.ok || !response?.body) {
  69. throw new error_1.DownloadHTTPError('Failed to download', response.status);
  70. }
  71. return response.body;
  72. }
  73. }
  74. exports.DefaultFetcher = DefaultFetcher;
  75. const writeBufferToStream = async (stream, buffer) => {
  76. return new Promise((resolve, reject) => {
  77. stream.write(buffer, (err) => {
  78. if (err) {
  79. reject(err);
  80. }
  81. resolve(true);
  82. });
  83. });
  84. };