kalai-attach 1.0.0
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/LICENSE +201 -0
- package/README.md +451 -0
- package/_i18n/i18n.properties +6 -0
- package/_i18n/i18n_ar.properties +6 -0
- package/_i18n/i18n_bg.properties +6 -0
- package/_i18n/i18n_cs.properties +6 -0
- package/_i18n/i18n_da.properties +6 -0
- package/_i18n/i18n_de.properties +6 -0
- package/_i18n/i18n_el.properties +6 -0
- package/_i18n/i18n_en.properties +6 -0
- package/_i18n/i18n_en_US_saptrc.properties +6 -0
- package/_i18n/i18n_es.properties +6 -0
- package/_i18n/i18n_es_MX.properties +6 -0
- package/_i18n/i18n_fi.properties +6 -0
- package/_i18n/i18n_fr.properties +6 -0
- package/_i18n/i18n_he.properties +6 -0
- package/_i18n/i18n_hr.properties +6 -0
- package/_i18n/i18n_hu.properties +6 -0
- package/_i18n/i18n_it.properties +6 -0
- package/_i18n/i18n_ja.properties +6 -0
- package/_i18n/i18n_kk.properties +6 -0
- package/_i18n/i18n_ko.properties +6 -0
- package/_i18n/i18n_ms.properties +6 -0
- package/_i18n/i18n_nl.properties +6 -0
- package/_i18n/i18n_no.properties +6 -0
- package/_i18n/i18n_pl.properties +6 -0
- package/_i18n/i18n_pt.properties +6 -0
- package/_i18n/i18n_ro.properties +6 -0
- package/_i18n/i18n_ru.properties +6 -0
- package/_i18n/i18n_sh.properties +6 -0
- package/_i18n/i18n_sk.properties +6 -0
- package/_i18n/i18n_sl.properties +6 -0
- package/_i18n/i18n_sv.properties +6 -0
- package/_i18n/i18n_th.properties +6 -0
- package/_i18n/i18n_tr.properties +6 -0
- package/_i18n/i18n_uk.properties +6 -0
- package/_i18n/i18n_vi.properties +6 -0
- package/_i18n/i18n_zh_CN.properties +6 -0
- package/_i18n/i18n_zh_TW.properties +6 -0
- package/_i18n/messages.properties +6 -0
- package/_i18n/messages_en_US_saptrc.properties +4 -0
- package/cds-plugin.js +4 -0
- package/db/data/sap.attachments-ScanStates.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_ar.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_bg.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_cs.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_da.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_de.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_el.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_en.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_en_US_saptrc.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_es.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_es_MX.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_fi.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_fr.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_he.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_hr.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_hu.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_it.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_ja.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_kk.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_ko.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_ms.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_nl.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_no.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_pl.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_pt.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_ro.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_ru.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_sh.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_sk.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_sl.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_sv.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_th.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_tr.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_uk.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_vi.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_zh_CN.csv +6 -0
- package/db/data/sap.attachments-ScanStates_texts_zh_TW.csv +6 -0
- package/db/index.cds +85 -0
- package/index.cds +1 -0
- package/lib/csn-runtime-extension.js +31 -0
- package/lib/generic-handlers.js +199 -0
- package/lib/helper.js +407 -0
- package/lib/mtx/server.js +583 -0
- package/lib/plugin.js +112 -0
- package/package.json +67 -0
- package/srv/aws-s3.js +249 -0
- package/srv/azure-blob-storage.js +202 -0
- package/srv/basic.js +331 -0
- package/srv/gcp.js +226 -0
- package/srv/malwareScanner-mocked.cds +3 -0
- package/srv/malwareScanner-mocked.js +127 -0
- package/srv/malwareScanner.cds +23 -0
- package/srv/malwareScanner.js +151 -0
- package/srv/object-store.js +44 -0
- package/srv/standard.js +3 -0
package/lib/helper.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
const axios = require('axios')
|
|
2
|
+
const https = require("https")
|
|
3
|
+
const crypto = require("crypto")
|
|
4
|
+
const stream = require('stream/promises')
|
|
5
|
+
const cds = require('@sap/cds')
|
|
6
|
+
const LOG = cds.log("attachments")
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates the presence of required Service Manager credentials
|
|
10
|
+
* @param {*} serviceManagerCreds - Service Manager credentials object
|
|
11
|
+
* @throws Will throw an error if validation fails
|
|
12
|
+
*/
|
|
13
|
+
function validateServiceManagerCredentials(serviceManagerCreds) {
|
|
14
|
+
if (!serviceManagerCreds) {
|
|
15
|
+
LOG.error('serviceManager.credentials is missing',
|
|
16
|
+
'Bind a Service Manager instance for separate object store mode')
|
|
17
|
+
throw new Error("Service Manager Instance is not bound")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const requiredSmFields = ['sm_url', 'url', 'clientid']
|
|
21
|
+
const missingSmFields = requiredSmFields.filter(field => !serviceManagerCreds[field])
|
|
22
|
+
|
|
23
|
+
if (missingSmFields.length > 0) {
|
|
24
|
+
LOG.error('serviceManager.credentials is missing a few fields. Passed object: ', serviceManagerCreds,
|
|
25
|
+
`Service Manager credentials missing: ${missingSmFields.join(', ')}`)
|
|
26
|
+
throw new Error(`Missing Service Manager credentials: ${missingSmFields.join(', ')}`)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validates the inputs required for fetching object store credentials
|
|
32
|
+
* @param {string} tenantID - Tenant ID
|
|
33
|
+
* @param {string} sm_url - Service Manager URL
|
|
34
|
+
* @param {string} token - Access token
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
function validateInputs(tenantID, sm_url, token) {
|
|
38
|
+
if (!tenantID) {
|
|
39
|
+
LOG.error('Tenant ID is required for object store credentials', null,
|
|
40
|
+
'Ensure multitenancy is properly configured and tenant context is available', { tenantID })
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!sm_url) {
|
|
45
|
+
LOG.error('serviceManager.credentials.sm_url', sm_url, false,
|
|
46
|
+
'Bind a Service Manager instance to your application')
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!token) {
|
|
51
|
+
LOG.error('Access token is required for Service Manager API', null,
|
|
52
|
+
'Check if token fetching completed successfully', { hasToken: !!token })
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fetches object store service binding from Service Manager
|
|
61
|
+
* @param {string} tenantID - Tenant ID
|
|
62
|
+
* @param {string?} token - Access token, if nothing is provided access token is fetched
|
|
63
|
+
* @returns {Promise<Array>} - Promise resolving to array of service bindings
|
|
64
|
+
*/
|
|
65
|
+
async function fetchObjectStoreBinding(tenantID, token) {
|
|
66
|
+
const serviceManagerCreds = cds.env.requires?.serviceManager?.credentials
|
|
67
|
+
|
|
68
|
+
validateServiceManagerCredentials(serviceManagerCreds)
|
|
69
|
+
|
|
70
|
+
const { sm_url, url, clientid, clientsecret, certificate, key, certurl } = serviceManagerCreds
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if (!token) {
|
|
74
|
+
LOG.debug('Fetching access token for tenant', { tenantID, sm_url: sm_url })
|
|
75
|
+
token = await fetchToken(url, clientid, clientsecret, certificate, key, certurl)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
LOG.debug('Fetching object store credentials', { tenantID, sm_url })
|
|
79
|
+
|
|
80
|
+
if (!validateInputs(tenantID, sm_url, token)) {
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
LOG.debug('Making Service Manager API call', {
|
|
85
|
+
tenantID,
|
|
86
|
+
endpoint: `${sm_url}/v1/service_bindings`,
|
|
87
|
+
labelQuery: `service eq 'OBJECT_STORE' and tenant_id eq '${tenantID}'`
|
|
88
|
+
})
|
|
89
|
+
const response = await axios.get(`${sm_url}/v1/service_bindings`, {
|
|
90
|
+
params: { labelQuery: `service eq 'OBJECT_STORE' and tenant_id eq '${tenantID}'` },
|
|
91
|
+
headers: {
|
|
92
|
+
'Accept': 'application/json',
|
|
93
|
+
'Authorization': `Bearer ${token}`,
|
|
94
|
+
'Content-Type': 'application/json'
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return response.data?.items || []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Retrieves object store credentials for a given tenant
|
|
103
|
+
* @param {string} tenantID - Tenant ID
|
|
104
|
+
* @returns {Promise<Object|null>} - Promise resolving to object store credentials or null
|
|
105
|
+
*/
|
|
106
|
+
async function getObjectStoreCredentials(tenantID) {
|
|
107
|
+
try {
|
|
108
|
+
const items = await fetchObjectStoreBinding(tenantID)
|
|
109
|
+
|
|
110
|
+
if (!items.length) {
|
|
111
|
+
LOG.error(`No object store service binding found for tenant`, null,
|
|
112
|
+
'Ensure an Object Store instance is subscribed and bound for this tenant',
|
|
113
|
+
{ tenantID, itemsFound: 0 })
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const credentials = items[0]
|
|
118
|
+
LOG.info('Object store credentials retrieved successfully', {
|
|
119
|
+
tenantID,
|
|
120
|
+
hasCredentials: !!credentials,
|
|
121
|
+
bucket: credentials?.credentials?.bucket
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return credentials
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const suggestion = error.response?.status === 401 ?
|
|
127
|
+
'Check Service Manager credentials and token validity' :
|
|
128
|
+
error.response?.status === 404 ?
|
|
129
|
+
'Verify Service Manager URL and API endpoint' :
|
|
130
|
+
'Check network connectivity and Service Manager instance health'
|
|
131
|
+
|
|
132
|
+
LOG.error('Failed to fetch object store credentials', error, suggestion, {
|
|
133
|
+
tenantID,
|
|
134
|
+
httpStatus: error.response?.status,
|
|
135
|
+
responseData: error.response?.data
|
|
136
|
+
})
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Fetches an OAuth token using either client credentials or MTLS
|
|
143
|
+
* @param {string} url - Token endpoint URL
|
|
144
|
+
* @param {string} clientid - Client ID
|
|
145
|
+
* @param {string} clientsecret - Client Secret
|
|
146
|
+
* @param {string} certificate - MTLS Certificate
|
|
147
|
+
* @param {string} key - MTLS Key
|
|
148
|
+
* @param {string} certURL - MTLS Certificate URL
|
|
149
|
+
* @returns {Promise<string>} - Promise resolving to access token
|
|
150
|
+
*/
|
|
151
|
+
async function fetchToken(url, clientid, clientsecret, certificate, key, certURL) {
|
|
152
|
+
LOG.info('Determining token fetch method', {
|
|
153
|
+
hasClientCredentials: !!(clientid && clientsecret),
|
|
154
|
+
hasMTLSCredentials: !!(certificate && key && certURL),
|
|
155
|
+
url,
|
|
156
|
+
clientid
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// Validate credentials
|
|
160
|
+
if (!clientid) {
|
|
161
|
+
LOG.error('serviceManager.credentials.clientid is missing',
|
|
162
|
+
'Check Service Manager service binding for client ID')
|
|
163
|
+
throw new Error("Client ID is required for token fetching")
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (certificate && key && certURL) {
|
|
167
|
+
LOG.debug('Using MTLS authentication for token fetch', { clientid, certURL })
|
|
168
|
+
return fetchTokenWithMTLS(certURL, clientid, certificate, key)
|
|
169
|
+
} else if (clientid && clientsecret) {
|
|
170
|
+
LOG.debug('Using client credentials authentication for token fetch', { clientid, url })
|
|
171
|
+
return fetchTokenWithClientSecret(url, clientid, clientsecret)
|
|
172
|
+
} else {
|
|
173
|
+
const suggestion = 'Ensure Service Manager binding includes either (clientid + clientsecret) or (certificate + key + certurl)'
|
|
174
|
+
LOG.error('Insufficient credentials for token fetching', null, suggestion, {
|
|
175
|
+
hasClientId: !!clientid,
|
|
176
|
+
hasClientSecret: !!clientsecret,
|
|
177
|
+
hasCertificate: !!certificate,
|
|
178
|
+
hasKey: !!key,
|
|
179
|
+
hasCertURL: !!certURL
|
|
180
|
+
})
|
|
181
|
+
throw new Error("Invalid credentials provided for token fetching.")
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Fetches OAuth token using client credentials flow
|
|
187
|
+
* @param {string} url - Token endpoint URL
|
|
188
|
+
* @param {string} clientid - Client ID
|
|
189
|
+
* @param {string} clientsecret - Client Secret
|
|
190
|
+
* @returns
|
|
191
|
+
*/
|
|
192
|
+
async function fetchTokenWithClientSecret(url, clientid, clientsecret) {
|
|
193
|
+
const startTime = Date.now()
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
LOG.debug('Initiating OAuth client credentials flow', {
|
|
197
|
+
endpoint: `${url}/oauth/token`,
|
|
198
|
+
clientid
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const headers = {
|
|
202
|
+
'Accept': 'application/json',
|
|
203
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const response = await axios.post(`${url}/oauth/token`, null, {
|
|
207
|
+
headers,
|
|
208
|
+
params: {
|
|
209
|
+
grant_type: "client_credentials",
|
|
210
|
+
client_id: clientid,
|
|
211
|
+
client_secret: clientsecret,
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const duration = Date.now() - startTime
|
|
216
|
+
LOG.debug('OAuth token fetched successfully', { clientid, duration, tokenType: response.data?.token_type })
|
|
217
|
+
|
|
218
|
+
return response.data.access_token
|
|
219
|
+
} catch (error) {
|
|
220
|
+
const duration = Date.now() - startTime
|
|
221
|
+
const suggestion = error.response?.status === 401 ?
|
|
222
|
+
'Verify Service Manager client credentials (clientid and clientsecret)' :
|
|
223
|
+
error.response?.status === 404 ?
|
|
224
|
+
'Check Service Manager URL is correct' :
|
|
225
|
+
'Verify Service Manager instance is running and accessible'
|
|
226
|
+
|
|
227
|
+
LOG.error(
|
|
228
|
+
'Failed to fetch OAuth token using client credentials', error,
|
|
229
|
+
suggestion,
|
|
230
|
+
{ clientid, duration, httpStatus: error.response?.status, errorMessage: error.message })
|
|
231
|
+
|
|
232
|
+
throw error
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Fetches OAuth token using MTLS authentication
|
|
238
|
+
* @param {string} certURL - Certificate URL
|
|
239
|
+
* @param {string} clientid - Client ID
|
|
240
|
+
* @param {string} certificate - MTLS Certificate
|
|
241
|
+
* @param {string} key - MTLS Key
|
|
242
|
+
* @returns {Promise<string>} - Promise resolving to access token
|
|
243
|
+
*/
|
|
244
|
+
async function fetchTokenWithMTLS(certURL, clientid, certificate, key) {
|
|
245
|
+
const startTime = Date.now()
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
LOG.debug('Initiating MTLS authentication flow', {
|
|
249
|
+
endpoint: `${certURL}/oauth/token`,
|
|
250
|
+
clientid
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const requestBody = new URLSearchParams({
|
|
254
|
+
grant_type: 'client_credentials',
|
|
255
|
+
response_type: 'token',
|
|
256
|
+
client_id: clientid
|
|
257
|
+
}).toString()
|
|
258
|
+
|
|
259
|
+
const options = {
|
|
260
|
+
headers: {
|
|
261
|
+
'Accept': 'application/json',
|
|
262
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
263
|
+
},
|
|
264
|
+
url: `${certURL}/oauth/token`,
|
|
265
|
+
method: 'POST',
|
|
266
|
+
data: requestBody,
|
|
267
|
+
httpsAgent: new https.Agent({
|
|
268
|
+
cert: certificate,
|
|
269
|
+
key: key
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const response = await axios(options)
|
|
274
|
+
const duration = Date.now() - startTime
|
|
275
|
+
|
|
276
|
+
if (!response.data?.access_token) {
|
|
277
|
+
LOG.error('MTLS token response missing access_token', null,
|
|
278
|
+
'Check MTLS certificate/key validity and Service Manager configuration',
|
|
279
|
+
{ clientid, duration, responseData: response.data })
|
|
280
|
+
throw new Error('Access token not found in MTLS token response')
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
LOG.debug('MTLS token fetched successfully', { clientid, duration, tokenType: response.data.token_type })
|
|
284
|
+
|
|
285
|
+
return response.data.access_token
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const duration = Date.now() - startTime
|
|
288
|
+
|
|
289
|
+
LOG.error(
|
|
290
|
+
'Failed to fetch OAuth token using MTLS', error,
|
|
291
|
+
'Check MTLS certificate, key, and Service Manager connectivity',
|
|
292
|
+
{ clientid, duration, httpStatus: error.response?.status, errorMessage: error.message })
|
|
293
|
+
|
|
294
|
+
throw error
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Checks if the given mimeType matches any of the allowedTypes patterns
|
|
300
|
+
* @param {Array<string>} allowedTypes - Array of allowed mime types (can include wildcards)
|
|
301
|
+
* @param {string} mimeType - Mime type to check
|
|
302
|
+
* @returns {boolean} - True if mimeType matches any allowedTypes, false otherwise
|
|
303
|
+
*/
|
|
304
|
+
function checkMimeTypeMatch(allowedTypes, mimeType) {
|
|
305
|
+
if (!allowedTypes || allowedTypes.length === 0) {
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (typeof allowedTypes === 'string') {
|
|
310
|
+
allowedTypes = [allowedTypes]
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (allowedTypes.includes('*/*')) {
|
|
314
|
+
return true
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Remove any parameters (e.g., "; charset=utf-8", "; boundary=...")
|
|
318
|
+
const baseMimeType = mimeType.split(';')[0].trim()
|
|
319
|
+
|
|
320
|
+
return allowedTypes.some(allowedType => {
|
|
321
|
+
if (allowedType.endsWith('/*')) {
|
|
322
|
+
const prefix = allowedType.slice(0, -2)
|
|
323
|
+
return baseMimeType.startsWith(prefix + '/')
|
|
324
|
+
} else {
|
|
325
|
+
return baseMimeType === allowedType
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function computeHash(input) {
|
|
331
|
+
const hash = crypto.createHash('sha256')
|
|
332
|
+
|
|
333
|
+
// Connect the output of the `input` stream to the input of `hash`
|
|
334
|
+
// and let Node.js do the streaming
|
|
335
|
+
await stream.pipeline(input, hash)
|
|
336
|
+
|
|
337
|
+
return hash.digest('hex')
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
const multipliers = {}
|
|
342
|
+
multipliers.B = 1;
|
|
343
|
+
multipliers.KB = multipliers.B * 1024;
|
|
344
|
+
multipliers.MB = multipliers.KB * 1024;
|
|
345
|
+
multipliers.GB = multipliers.MB * 1024;
|
|
346
|
+
multipliers.TB = multipliers.GB * 1024;
|
|
347
|
+
multipliers.PB = multipliers.TB * 1024;
|
|
348
|
+
multipliers.EB = multipliers.PB * 1024;
|
|
349
|
+
multipliers.ZB = multipliers.EB * 1024;
|
|
350
|
+
|
|
351
|
+
const MAX_FILE_SIZE = 419430400 //400 MB in bytes
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Converts a byte size string into the corresponding number.
|
|
355
|
+
*
|
|
356
|
+
* @param {string} size 20mb for example
|
|
357
|
+
* @returns Byte size or null if nothing was found
|
|
358
|
+
*/
|
|
359
|
+
function sizeInBytes(size, target) {
|
|
360
|
+
if (!size || (typeof size !== 'string' && typeof size !== 'number')) {
|
|
361
|
+
LOG.warn(`Could not determine the maximum byte size for the content of ${target}`)
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (typeof size === 'number') {
|
|
366
|
+
return size
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const value = parseFloat(size);
|
|
370
|
+
if (isNaN(value)) {
|
|
371
|
+
LOG.warn(`Could not determine the maximum byte size for the content of ${target}`)
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const unitMatches = size.toUpperCase().match(/([KMGTPEZ]I?)?B$/)
|
|
376
|
+
// Remove any optional "i" from the unit of measurement (ex, MiB).
|
|
377
|
+
const unit = unitMatches[0]?.replace(/i/i, "")
|
|
378
|
+
|
|
379
|
+
if (!unit) {
|
|
380
|
+
LOG.warn(`Could not determine the maximum byte size for the content of ${target}`)
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return value * multipliers[unit]
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function traverseEntity(root, path) {
|
|
388
|
+
let current = root
|
|
389
|
+
for (const part of path) {
|
|
390
|
+
if (!current.elements || !current.elements[part]) return undefined
|
|
391
|
+
current = current.elements[part]._target
|
|
392
|
+
if (!current) return undefined
|
|
393
|
+
}
|
|
394
|
+
return current
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
module.exports = {
|
|
398
|
+
fetchToken,
|
|
399
|
+
getObjectStoreCredentials,
|
|
400
|
+
computeHash,
|
|
401
|
+
sizeInBytes,
|
|
402
|
+
fetchObjectStoreBinding,
|
|
403
|
+
validateServiceManagerCredentials,
|
|
404
|
+
checkMimeTypeMatch,
|
|
405
|
+
traverseEntity,
|
|
406
|
+
MAX_FILE_SIZE
|
|
407
|
+
}
|