"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.pattern2filter = pattern2filter;
exports.isLocalHost = isLocalHost;
exports.netMatches = netMatches;
exports.makeNetMatcher = makeNetMatcher;
exports.same = same;
exports.asyncGeneratorToReadable = asyncGeneratorToReadable;
exports.apiAssertTypes = apiAssertTypes;
exports.createStreamLimiter = createStreamLimiter;
exports.retrySync = retrySync;
const path_1 = require("path");
__exportStar(require("./util-http"), exports);
__exportStar(require("./util-files"), exports);
__exportStar(require("./fileAttr"), exports);
__exportStar(require("./cross"), exports);
__exportStar(require("./debounceAsync"), exports);
__exportStar(require("./AsapStream"), exports);
const stream_1 = require("stream");
const node_net_1 = require("node:net");
const apiMiddleware_1 = require("./apiMiddleware");
const const_1 = require("./const");
const cross_1 = require("./cross");
const net_1 = require("net");
const lodash_1 = __importDefault(require("lodash"));
function pattern2filter(pattern) {
    const matcher = (0, cross_1.makeMatcher)(pattern.includes('*') ? pattern // if you specify *, we'll respect its position
        : pattern.split('|').map(x => `*${x}*`).join('|'), false, false);
    return (s) => !pattern || matcher((0, path_1.basename)(s || ''));
}
function isLocalHost(c) {
    const ip = typeof c === 'string' ? c : c.ip;
    return ip && (0, cross_1.isIpLocalHost)(ip);
}
// this will memory-leak over mask, so be careful with what you use this. Object is 3x faster than _.memoize
function netMatches(ip, mask, emptyMaskReturns = false) {
    const cache = netMatches.cache ||= {};
    return (cache[mask + (emptyMaskReturns ? '1' : '0')] ||= makeNetMatcher(mask, emptyMaskReturns))(ip); // cache the matcher
}
function makeNetMatcher(mask, emptyMaskReturns = false) {
    if (!mask)
        return () => emptyMaskReturns;
    mask = mask.replaceAll(' ', '');
    mask = mask.replace('localhost', '::1|127.0.0.1');
    try {
        if (!/\/|-(?![^\[]*\])/.test(mask)) { // when no CIDR and no ranges are used, then we use standard matcher, otherwise BlockList. For "-" we must skip those inside []
            if (/[^.:\da-fA-F*?|()!]/.test(mask))
                throw mask;
            return (0, cross_1.makeMatcher)(mask);
        }
        const all = mask.split('|');
        const neg = all[0]?.[0] === '!';
        if (neg)
            all[0] = all[0].slice(1);
        const bl = new node_net_1.BlockList();
        for (const x of all) {
            const m = /^([.:\da-f]+)(?:\/(\d+)|-([.:\da-f]+)|)$/i.exec(x); // parse cidr or range
            if (!m)
                throw x; // we don't support wildcards in this case
            const address = (0, cross_1.try_)(() => parseAddress(m[1]), () => { throw m[1]; });
            if (!address)
                continue;
            if (m[2])
                try {
                    bl.addSubnet(address, Number(m[2]));
                }
                catch {
                    throw x;
                }
            else if (m[3])
                try {
                    bl.addRange(address, parseAddress(m[3]));
                }
                catch {
                    throw m[3];
                }
            else
                bl.addAddress(address);
        }
        return (ip) => {
            try {
                return neg !== bl.check(parseAddress(ip));
            }
            catch {
                console.error("Invalid address ", ip);
                return false;
            }
        };
    }
    catch (e) {
        throw "error in net-mask: " + e;
    }
}
// can throw ERR_INVALID_ADDRESS
function parseAddress(s) {
    return new node_net_1.SocketAddress({ address: s, family: (0, net_1.isIPv6)(s) ? 'ipv6' : 'ipv4' });
}
function same(a, b) {
    return lodash_1.default.isEqual(a, b);
}
function asyncGeneratorToReadable(generator) {
    const iterator = generator[Symbol.asyncIterator]();
    return new stream_1.Readable({
        objectMode: true,
        destroy() {
            void iterator.return?.();
        },
        read() {
            iterator.next().then(it => {
                if (it.done)
                    this.emit('ending');
                return this.push(it.done ? null : it.value);
            });
        }
    });
}
function apiAssertTypes(paramsByType) {
    for (const [types, params] of Object.entries(paramsByType)) {
        if (!lodash_1.default.isPlainObject(params))
            throw "invalid apiAssertTypes call";
        for (const [name, val] of Object.entries(params))
            if (!types.split('_').some(t => t === 'array' ? Array.isArray(val) : t === 'object' ? lodash_1.default.isPlainObject(val) : typeof val === t))
                throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad ' + name);
    }
}
function createStreamLimiter(limit) {
    let got = 0;
    return new stream_1.Transform({
        transform(chunk, enc, done) {
            const left = limit - got;
            got += chunk.length;
            if (left > 0) {
                this.push(chunk.length > left ? chunk.slice(0, left) : chunk);
                if (got >= limit)
                    this.end();
            }
            done();
        }
    });
}
function retrySync(cb, attempts = 20, sleep = 500) {
    const sleepSyncBuffer = new Int32Array(new SharedArrayBuffer(4));
    for (let retry = 0;; retry++) {
        try {
            return cb();
        }
        catch (e) {
            if (e?.code !== 'EBUSY' || retry >= attempts)
                throw e;
            Atomics.wait(sleepSyncBuffer, 0, 0, sleep);
        }
    }
}
