File size: 4,844 Bytes
f316cce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MiscService = void 0;
const tsyringe_1 = require("tsyringe");
const async_service_1 = require("civkit/async-service");
const civ_rpc_1 = require("civkit/civ-rpc");
const errors_1 = require("../shared/lib/errors");
const node_net_1 = require("node:net");
const ip_1 = require("../utils/ip");
const logger_1 = require("./logger");
const promises_1 = require("node:dns/promises");
const threaded_1 = require("./threaded");
const normalizeUrl = require('@esm2cjs/normalize-url').default;
let MiscService = class MiscService extends async_service_1.AsyncService {
    constructor(globalLogger) {
        super(...arguments);
        this.globalLogger = globalLogger;
        this.logger = this.globalLogger.child({ service: this.constructor.name });
    }
    async init() {
        await this.dependencyReady();
        this.emit('ready');
    }
    async assertNormalizedUrl(input) {
        let result;
        try {
            result = new URL(normalizeUrl(input, {
                stripWWW: false,
                removeTrailingSlash: false,
                removeSingleSlash: false,
                sortQueryParameters: false,
            }));
        }
        catch (err) {
            throw new civ_rpc_1.ParamValidationError({
                message: `${err}`,
                path: 'url'
            });
        }
        if (!['http:', 'https:', 'blob:'].includes(result.protocol)) {
            throw new civ_rpc_1.ParamValidationError({
                message: `Invalid protocol ${result.protocol}`,
                path: 'url'
            });
        }
        const normalizedHostname = result.hostname.startsWith('[') ? result.hostname.slice(1, -1) : result.hostname;
        let ips = [];
        const isIp = (0, node_net_1.isIP)(normalizedHostname);
        if (isIp) {
            ips.push(normalizedHostname);
        }
        if ((result.hostname === 'localhost') ||
            (isIp && (0, ip_1.isIPInNonPublicRange)(normalizedHostname))) {
            this.logger.warn(`Suspicious action: Request to localhost or non-public IP: ${normalizedHostname}`, { href: result.href });
            throw new errors_1.SecurityCompromiseError({
                message: `Suspicious action: Request to localhost or non-public IP: ${normalizedHostname}`,
                path: 'url'
            });
        }
        if (!isIp && result.protocol !== 'blob:') {
            const resolved = await (0, promises_1.lookup)(result.hostname, { all: true }).catch((err) => {
                if (err.code === 'ENOTFOUND') {
                    return Promise.reject(new civ_rpc_1.ParamValidationError({
                        message: `Domain '${result.hostname}' could not be resolved`,
                        path: 'url'
                    }));
                }
                return;
            });
            if (resolved) {
                for (const x of resolved) {
                    if ((0, ip_1.isIPInNonPublicRange)(x.address)) {
                        this.logger.warn(`Suspicious action: Domain resolved to non-public IP: ${result.hostname} => ${x.address}`, { href: result.href, ip: x.address });
                        throw new errors_1.SecurityCompromiseError({
                            message: `Suspicious action: Domain resolved to non-public IP: ${x.address}`,
                            path: 'url'
                        });
                    }
                    ips.push(x.address);
                }
            }
        }
        return {
            url: result,
            ips
        };
    }
};
exports.MiscService = MiscService;
__decorate([
    (0, threaded_1.Threaded)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MiscService.prototype, "assertNormalizedUrl", null);
exports.MiscService = MiscService = __decorate([
    (0, tsyringe_1.singleton)(),
    __metadata("design:paramtypes", [logger_1.GlobalLogger])
], MiscService);
//# sourceMappingURL=misc.js.map