2022-09-17 18:27:08 +00:00
|
|
|
import * as fs from 'node:fs';
|
|
|
|
import * as stream from 'node:stream';
|
|
|
|
import * as util from 'node:util';
|
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
|
|
import IPCIDR from 'ip-cidr';
|
|
|
|
import PrivateIp from 'private-ip';
|
|
|
|
import chalk from 'chalk';
|
2023-01-23 23:31:02 +00:00
|
|
|
import { buildConnector } from 'undici';
|
2022-09-17 18:27:08 +00:00
|
|
|
import { DI } from '@/di-symbols.js';
|
2022-09-20 20:33:11 +00:00
|
|
|
import type { Config } from '@/config.js';
|
2023-01-12 12:03:02 +00:00
|
|
|
import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js';
|
2022-09-17 18:27:08 +00:00
|
|
|
import { createTemp } from '@/misc/create-temp.js';
|
|
|
|
import { StatusError } from '@/misc/status-error.js';
|
2022-09-18 14:07:41 +00:00
|
|
|
import { LoggerService } from '@/core/LoggerService.js';
|
|
|
|
import type Logger from '@/logger.js';
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
const pipeline = util.promisify(stream.pipeline);
|
2022-12-04 06:03:09 +00:00
|
|
|
import { bindThis } from '@/decorators.js';
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class DownloadService {
|
2022-09-18 18:11:50 +00:00
|
|
|
private logger: Logger;
|
2023-01-12 12:03:02 +00:00
|
|
|
private undiciFetcher: UndiciFetcher;
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
@Inject(DI.config)
|
|
|
|
private config: Config,
|
|
|
|
|
|
|
|
private httpRequestService: HttpRequestService,
|
2022-09-18 14:07:41 +00:00
|
|
|
private loggerService: LoggerService,
|
2022-09-17 18:27:08 +00:00
|
|
|
) {
|
2022-09-18 18:11:50 +00:00
|
|
|
this.logger = this.loggerService.getLogger('download');
|
2023-01-12 12:03:02 +00:00
|
|
|
|
2023-01-23 23:31:02 +00:00
|
|
|
this.undiciFetcher = this.httpRequestService.createFetcher({
|
|
|
|
connect: process.env.NODE_ENV === 'development' ?
|
|
|
|
this.httpRequestService.clientDefaults.connect
|
|
|
|
:
|
|
|
|
this.httpRequestService.getConnectorWithIpCheck(
|
|
|
|
buildConnector({
|
|
|
|
...this.httpRequestService.clientDefaults.connect,
|
|
|
|
}),
|
|
|
|
(ip) => !this.isPrivateIp(ip),
|
|
|
|
),
|
|
|
|
bodyTimeout: 30 * 1000,
|
|
|
|
}, {
|
|
|
|
connect: this.httpRequestService.clientDefaults.connect,
|
|
|
|
}, this.logger);
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
|
2022-12-04 06:03:09 +00:00
|
|
|
@bindThis
|
2022-09-17 18:27:08 +00:00
|
|
|
public async downloadUrl(url: string, path: string): Promise<void> {
|
2022-12-30 03:00:50 +00:00
|
|
|
this.logger.info(`Downloading ${chalk.cyan(url)} to ${chalk.cyanBright(path)} ...`);
|
2023-01-12 12:03:02 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
const timeout = 30 * 1000;
|
|
|
|
const operationTimeout = 60 * 1000;
|
|
|
|
const maxSize = this.config.maxFileSize ?? 262144000;
|
2023-01-12 12:03:02 +00:00
|
|
|
|
|
|
|
const response = await this.undiciFetcher.fetch(url);
|
|
|
|
|
|
|
|
if (response.body === null) {
|
|
|
|
throw new StatusError('No body', 400, 'No body');
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
2023-01-12 12:03:02 +00:00
|
|
|
|
|
|
|
await pipeline(stream.Readable.fromWeb(response.body), fs.createWriteStream(path));
|
|
|
|
|
2022-09-18 18:11:50 +00:00
|
|
|
this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
|
2022-12-04 06:03:09 +00:00
|
|
|
@bindThis
|
2022-09-17 18:27:08 +00:00
|
|
|
public async downloadTextFile(url: string): Promise<string> {
|
|
|
|
// Create temp file
|
|
|
|
const [path, cleanup] = await createTemp();
|
|
|
|
|
2022-09-18 18:11:50 +00:00
|
|
|
this.logger.info(`text file: Temp file is ${path}`);
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
// write content at URL to temp file
|
|
|
|
await this.downloadUrl(url, path);
|
|
|
|
|
|
|
|
const text = await util.promisify(fs.readFile)(path, 'utf8');
|
|
|
|
|
|
|
|
return text;
|
|
|
|
} finally {
|
|
|
|
cleanup();
|
|
|
|
}
|
|
|
|
}
|
2023-01-12 12:03:02 +00:00
|
|
|
|
2022-12-04 06:03:09 +00:00
|
|
|
@bindThis
|
2022-09-18 18:11:50 +00:00
|
|
|
private isPrivateIp(ip: string): boolean {
|
2022-09-17 18:27:08 +00:00
|
|
|
for (const net of this.config.allowedPrivateNetworks ?? []) {
|
|
|
|
const cidr = new IPCIDR(net);
|
|
|
|
if (cidr.contains(ip)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-12 12:03:02 +00:00
|
|
|
return PrivateIp(ip) ?? false;
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
}
|