@unavatar/core 3.46.0 → 3.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/avatar/auto.js +31 -2
- package/src/index.js +12 -15
- package/src/providers/bimi.js +3 -6
- package/src/util/got.js +33 -2
- package/src/util/is-reserved-ip.js +9 -7
- package/src/util/reachable-url.js +6 -5
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@unavatar/core",
|
|
3
3
|
"description": "Get unified user avatar from social networks, including Instagram, SoundCloud, Telegram, Twitter, YouTube & more.",
|
|
4
4
|
"homepage": "https://unavatar.io",
|
|
5
|
-
"version": "3.46.
|
|
5
|
+
"version": "3.46.1",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
package/src/avatar/auto.js
CHANGED
|
@@ -8,6 +8,7 @@ const isEmail = require('is-email-like')
|
|
|
8
8
|
const pTimeout = require('p-timeout')
|
|
9
9
|
const pAny = require('p-any')
|
|
10
10
|
|
|
11
|
+
const { RESERVED_ADDRESS_CODE } = require('../util/is-reserved-ip')
|
|
11
12
|
const httpStatus = require('../util/http-status')
|
|
12
13
|
const isIterable = require('../util/is-iterable')
|
|
13
14
|
const ExtendableError = require('../util/error')
|
|
@@ -23,9 +24,29 @@ const getInputType = input => {
|
|
|
23
24
|
return 'username'
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
const factory = ({
|
|
27
|
+
const factory = ({
|
|
28
|
+
constants,
|
|
29
|
+
providers,
|
|
30
|
+
providerTiers,
|
|
31
|
+
reachableUrl,
|
|
32
|
+
isReservedIp
|
|
33
|
+
}) => {
|
|
27
34
|
const { REQUEST_TIMEOUT } = constants
|
|
28
35
|
|
|
36
|
+
const refuseReservedAddress = provider => {
|
|
37
|
+
throw new ExtendableError({
|
|
38
|
+
message: 'The URL points to a reserved address.',
|
|
39
|
+
provider,
|
|
40
|
+
statusCode: httpStatus.FORBIDDEN
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const assertPublicUrl = async (url, provider) => {
|
|
45
|
+
if (await isReservedIp(new URL(url).hostname)) {
|
|
46
|
+
refuseReservedAddress(provider)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
const getAvatarContent = provider => async output => {
|
|
30
51
|
if (typeof output !== 'string' || output === '') {
|
|
31
52
|
const message =
|
|
@@ -49,7 +70,12 @@ const factory = ({ constants, providers, providerTiers, reachableUrl }) => {
|
|
|
49
70
|
})
|
|
50
71
|
}
|
|
51
72
|
|
|
52
|
-
|
|
73
|
+
await assertPublicUrl(output, provider)
|
|
74
|
+
|
|
75
|
+
const { statusCode, url, reservedAddress } = await reachableUrl(output)
|
|
76
|
+
|
|
77
|
+
if (reservedAddress) refuseReservedAddress(provider)
|
|
78
|
+
await assertPublicUrl(url, provider)
|
|
53
79
|
|
|
54
80
|
if (!reachableUrl.isReachable({ statusCode })) {
|
|
55
81
|
throw new ExtendableError({
|
|
@@ -73,6 +99,9 @@ const factory = ({ constants, providers, providerTiers, reachableUrl }) => {
|
|
|
73
99
|
.then(getAvatarContent(provider))
|
|
74
100
|
.catch(error => {
|
|
75
101
|
isIterable.forEach(error, error => {
|
|
102
|
+
if (error.code === RESERVED_ADDRESS_CODE) {
|
|
103
|
+
error.statusCode = httpStatus.FORBIDDEN
|
|
104
|
+
}
|
|
76
105
|
error.statusCode = error.statusCode ?? error.response?.statusCode
|
|
77
106
|
error.provider = provider
|
|
78
107
|
})
|
package/src/index.js
CHANGED
|
@@ -29,23 +29,20 @@ module.exports = ({
|
|
|
29
29
|
resolver: dnsResolver
|
|
30
30
|
})
|
|
31
31
|
const isReservedIp = require('./util/is-reserved-ip')({ cacheableLookup })
|
|
32
|
-
const got = require('./util/got')({ cacheableLookup })
|
|
32
|
+
const got = require('./util/got')({ cacheableLookup, isReservedIp })
|
|
33
33
|
const reachableUrl = require('./util/reachable-url')({
|
|
34
34
|
got,
|
|
35
35
|
pingCache: cache.pingCache
|
|
36
36
|
})
|
|
37
37
|
const createBrowser = require('./util/browserless')(constants)
|
|
38
38
|
const getHTML = require('./util/html-get')({ createBrowser, got })
|
|
39
|
-
const {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
onFetchHTML,
|
|
47
|
-
userAgent
|
|
48
|
-
})
|
|
39
|
+
const { createHtmlProvider, getOgImage, NOT_FOUND } =
|
|
40
|
+
require('./util/html-provider')({
|
|
41
|
+
...constants,
|
|
42
|
+
getHTML,
|
|
43
|
+
onFetchHTML,
|
|
44
|
+
userAgent
|
|
45
|
+
})
|
|
49
46
|
|
|
50
47
|
const providerCtx = {
|
|
51
48
|
constants,
|
|
@@ -60,15 +57,15 @@ module.exports = ({
|
|
|
60
57
|
githubSearchCache: cache.githubSearchCache,
|
|
61
58
|
itunesSearchCache: cache.itunesSearchCache
|
|
62
59
|
}
|
|
63
|
-
const { providers, providerTiers, providersBy } =
|
|
64
|
-
providerCtx
|
|
65
|
-
)
|
|
60
|
+
const { providers, providerTiers, providersBy } =
|
|
61
|
+
require('./providers')(providerCtx)
|
|
66
62
|
|
|
67
63
|
const { auto, getInputType, getAvatar } = require('./avatar/auto')({
|
|
68
64
|
constants,
|
|
69
65
|
providers,
|
|
70
66
|
providerTiers,
|
|
71
|
-
reachableUrl
|
|
67
|
+
reachableUrl,
|
|
68
|
+
isReservedIp
|
|
72
69
|
})
|
|
73
70
|
|
|
74
71
|
const unavatar = input => auto(getInputType(input))(input, {})
|
package/src/providers/bimi.js
CHANGED
|
@@ -4,18 +4,15 @@ const createGetLogo = require('bimi-url')
|
|
|
4
4
|
|
|
5
5
|
const parseInput = input => input.slice(input.lastIndexOf('@') + 1)
|
|
6
6
|
|
|
7
|
-
module.exports = ({ bimiCache, dnsResolver, got
|
|
7
|
+
module.exports = ({ bimiCache, dnsResolver, got }) => {
|
|
8
8
|
const getLogo = createGetLogo({
|
|
9
9
|
gotOpts: got.gotOpts,
|
|
10
10
|
keyvOpts: bimiCache,
|
|
11
11
|
resolveTxt: hostname => dnsResolver.resolveTxt(hostname)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
if (!logoUrl) return undefined
|
|
17
|
-
if (await isReservedIp(new URL(logoUrl).hostname)) return undefined
|
|
18
|
-
return logoUrl
|
|
14
|
+
return function bimi (input) {
|
|
15
|
+
return getLogo(parseInput(input))
|
|
19
16
|
}
|
|
20
17
|
}
|
|
21
18
|
|
package/src/util/got.js
CHANGED
|
@@ -5,6 +5,8 @@ const tlsHook = require('https-tls/hook')
|
|
|
5
5
|
const uaHints = require('ua-hints')
|
|
6
6
|
const got = require('got')
|
|
7
7
|
|
|
8
|
+
const { RESERVED_ADDRESS_CODE } = require('./is-reserved-ip')
|
|
9
|
+
|
|
8
10
|
const topUserAgents = require('top-user-agents')
|
|
9
11
|
const randomUserAgent = uniqueRandomArray(topUserAgents)
|
|
10
12
|
|
|
@@ -36,11 +38,40 @@ const userAgentHook = options => {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
module.exports = ({ cacheableLookup }) => {
|
|
41
|
+
module.exports = ({ cacheableLookup, isReservedIp }) => {
|
|
42
|
+
/**
|
|
43
|
+
* The refused address is reported on `options.context` because a caller
|
|
44
|
+
* reading the outcome through `reachable-url` never sees the rejection: it is
|
|
45
|
+
* swallowed there and served back as a response.
|
|
46
|
+
*
|
|
47
|
+
* Only a context the caller owns is written: the one got defaults to is
|
|
48
|
+
* frozen and shared between requests, so asking to be told is what passing a
|
|
49
|
+
* context means.
|
|
50
|
+
*
|
|
51
|
+
* got replaces the error with a RequestError that keeps the message and the
|
|
52
|
+
* code, so the refusal is named by a code rather than by its type.
|
|
53
|
+
*/
|
|
54
|
+
const reservedAddressHook = async options => {
|
|
55
|
+
const hostname = options.url?.hostname
|
|
56
|
+
if (hostname && (await isReservedIp(hostname))) {
|
|
57
|
+
if (Object.isExtensible(options.context)) {
|
|
58
|
+
options.context.reservedAddress = hostname
|
|
59
|
+
}
|
|
60
|
+
const error = new Error(
|
|
61
|
+
`Refusing to request a reserved address: ${hostname}`
|
|
62
|
+
)
|
|
63
|
+
error.code = RESERVED_ADDRESS_CODE
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
const gotOpts = {
|
|
41
69
|
dnsCache: cacheableLookup,
|
|
42
70
|
https: { rejectUnauthorized: false },
|
|
43
|
-
hooks: {
|
|
71
|
+
hooks: {
|
|
72
|
+
beforeRequest: [reservedAddressHook, userAgentHook, tlsHook],
|
|
73
|
+
beforeRedirect: [reservedAddressHook]
|
|
74
|
+
}
|
|
44
75
|
}
|
|
45
76
|
|
|
46
77
|
const instance = got.extend(gotOpts)
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const ip = require('ipaddr.js')
|
|
4
4
|
|
|
5
|
+
const unbracket = hostname =>
|
|
6
|
+
hostname.startsWith('[') && hostname.endsWith(']')
|
|
7
|
+
? hostname.slice(1, -1)
|
|
8
|
+
: hostname
|
|
9
|
+
|
|
5
10
|
module.exports = ({ cacheableLookup }) => {
|
|
6
11
|
const getIpAddress = async hostname => {
|
|
7
12
|
if (ip.IPv4.isIPv4(hostname)) return hostname
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
hostname.endsWith(']') &&
|
|
11
|
-
ip.IPv6.isIPv6(hostname.slice(1, -1))
|
|
12
|
-
) {
|
|
13
|
-
return hostname.slice(1, -1)
|
|
14
|
-
}
|
|
13
|
+
const literal = unbracket(hostname)
|
|
14
|
+
if (ip.IPv6.isIPv6(literal)) return literal
|
|
15
15
|
const { address } = await cacheableLookup.lookupAsync(hostname)
|
|
16
16
|
return address
|
|
17
17
|
}
|
|
@@ -21,3 +21,5 @@ module.exports = ({ cacheableLookup }) => {
|
|
|
21
21
|
return ip.process(ipAddress).range() !== 'unicast'
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
module.exports.RESERVED_ADDRESS_CODE = 'ERESERVEDADDRESSRANGE'
|
|
@@ -7,11 +7,12 @@ module.exports = ({ got, pingCache }) => {
|
|
|
7
7
|
value: ({ url, statusCode }) => ({ url, statusCode })
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
const reachableUrl = (url, opts) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
10
|
+
const reachableUrl = async (url, opts) => {
|
|
11
|
+
const context = {}
|
|
12
|
+
const response = await pingUrl(url, { ...got.gotOpts, ...opts, context })
|
|
13
|
+
const { reservedAddress } = context
|
|
14
|
+
return reservedAddress ? { ...response, reservedAddress } : response
|
|
15
|
+
}
|
|
15
16
|
|
|
16
17
|
reachableUrl.isReachable = createPingUrl.isReachable
|
|
17
18
|
|