-
-
Notifications
You must be signed in to change notification settings - Fork 270
/
Copy pathmisc.ts
144 lines (134 loc) · 5.15 KB
/
misc.ts
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
import { basename } from 'path'
import Koa from 'koa'
import { Connection } from './connections'
import assert from 'assert'
export * from './util-http'
export * from './util-files'
export * from './fileAttr'
export * from './cross'
export * from './debounceAsync'
import { Readable, Transform } from 'stream'
import { SocketAddress, BlockList } from 'node:net'
import { ApiError } from './apiMiddleware'
import { HTTP_BAD_REQUEST } from './const'
import { isIpLocalHost, makeMatcher, try_ } from './cross'
import { isIPv6 } from 'net'
import _ from 'lodash'
export function pattern2filter(pattern: string){
const matcher = makeMatcher(pattern.includes('*') ? pattern // if you specify *, we'll respect its position
: pattern.split('|').map(x => `*${x}*`).join('|'))
return (s: string) =>
!pattern || matcher(basename(s||''))
}
export function isLocalHost(c: Connection | Koa.Context | string) {
const ip = typeof c === 'string' ? c : c.socket.remoteAddress // don't use Context.ip as it is subject to proxied ips, and that's no use for localhost detection
return ip && isIpLocalHost(ip)
}
// this will memory-leak over mask, so be careful with what you use this. Object is 3x faster than _.memoize
export function netMatches(ip: string, mask: string, emptyMaskReturns=false) {
const cache = (netMatches as any).cache ||= {}
return (cache[mask + (emptyMaskReturns ? '1' : '0')] ||= makeNetMatcher(mask, emptyMaskReturns))(ip) // cache the matcher
}
export function makeNetMatcher(mask: string, 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 makeMatcher(mask)
}
const all = mask.split('|')
const neg = all[0]?.[0] === '!'
if (neg)
all[0] = all[0]!.slice(1)
const bl = new 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 = 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[2]!)) }
catch { throw m[2] }
else
bl.addAddress(address)
}
return (ip: string) => {
try { return neg !== bl.check(parseAddress(ip)) }
catch {
console.error("invalid address ", ip)
return false
}
}
}
catch(e: any) {
throw "error in net-mask: " + e
}
}
// can throw ERR_INVALID_ADDRESS
function parseAddress(s: string) {
return new SocketAddress({ address: s, family: isIPv6(s) ? 'ipv6' : 'ipv4' })
}
export function same(a: any, b: any) {
return _.isEqual(a, b)
}
export function asyncGeneratorToReadable<T>(generator: AsyncIterable<T>) {
const iterator = generator[Symbol.asyncIterator]()
return new 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)
})
}
})
}
// produces as promises resolve, not sequentially
export class AsapStream<T> extends Readable {
finished = false
constructor(private promises: Promise<T>[]) {
super({ objectMode: true })
}
_read() {
if (this.finished) return
this.finished = true
for (const p of this.promises)
p.then(x => x !== undefined && this.push(x),
e => this.emit('error', e) )
Promise.allSettled(this.promises).then(() => this.push(null))
}
}
export function apiAssertTypes(paramsByType: { [type:string]: { [name:string]: any } }) {
for (const [types,params] of Object.entries(paramsByType))
for (const [name,val] of Object.entries(params))
if (! types.split('_').some(type => type === 'array' ? Array.isArray(val) : typeof val === type))
throw new ApiError(HTTP_BAD_REQUEST, 'bad ' + name)
}
export function createStreamLimiter(limit: number) {
let got = 0
return new 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()
}
})
}