@hanzo/s3 0.6.3 → 8.0.7
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 +202 -0
- package/MAINTAINERS.md +62 -0
- package/README.md +262 -0
- package/README_zh_CN.md +192 -0
- package/dist/esm/AssumeRoleProvider.d.mts +86 -0
- package/dist/esm/AssumeRoleProvider.mjs +183 -0
- package/dist/esm/CredentialProvider.d.mts +22 -0
- package/dist/esm/CredentialProvider.mjs +48 -0
- package/dist/esm/Credentials.d.mts +22 -0
- package/dist/esm/Credentials.mjs +38 -0
- package/dist/esm/IamAwsProvider.d.mts +27 -0
- package/dist/esm/IamAwsProvider.mjs +189 -0
- package/dist/esm/errors.d.mts +82 -0
- package/dist/esm/errors.mjs +117 -0
- package/dist/esm/helpers.d.mts +156 -0
- package/dist/esm/helpers.mjs +218 -0
- package/dist/esm/internal/async.d.mts +9 -0
- package/dist/esm/internal/async.mjs +14 -0
- package/dist/esm/internal/callbackify.d.mts +1 -0
- package/dist/esm/internal/callbackify.mjs +15 -0
- package/dist/esm/internal/client.d.mts +394 -0
- package/dist/esm/internal/client.mjs +3007 -0
- package/dist/esm/internal/copy-conditions.d.mts +10 -0
- package/dist/esm/internal/copy-conditions.mjs +25 -0
- package/dist/esm/internal/extensions.d.mts +18 -0
- package/dist/esm/internal/extensions.mjs +114 -0
- package/dist/esm/internal/helper.d.mts +177 -0
- package/dist/esm/internal/helper.mjs +552 -0
- package/dist/esm/internal/join-host-port.d.mts +11 -0
- package/dist/esm/internal/join-host-port.mjs +23 -0
- package/dist/esm/internal/post-policy.d.mts +17 -0
- package/dist/esm/internal/post-policy.mjs +98 -0
- package/dist/esm/internal/request.d.mts +11 -0
- package/dist/esm/internal/request.mjs +75 -0
- package/dist/esm/internal/response.d.mts +8 -0
- package/dist/esm/internal/response.mjs +16 -0
- package/dist/esm/internal/s3-endpoints.d.mts +38 -0
- package/dist/esm/internal/s3-endpoints.mjs +68 -0
- package/dist/esm/internal/type.d.mts +482 -0
- package/dist/esm/internal/type.mjs +30 -0
- package/dist/esm/internal/xml-parser.d.mts +93 -0
- package/dist/esm/internal/xml-parser.mjs +819 -0
- package/dist/esm/notification.d.mts +58 -0
- package/dist/esm/notification.mjs +209 -0
- package/dist/esm/s3.d.mts +40 -0
- package/dist/esm/s3.mjs +86 -0
- package/dist/esm/signing.d.mts +5 -0
- package/dist/esm/signing.mjs +258 -0
- package/dist/main/AssumeRoleProvider.d.ts +86 -0
- package/dist/main/AssumeRoleProvider.js +191 -0
- package/dist/main/CredentialProvider.d.ts +22 -0
- package/dist/main/CredentialProvider.js +55 -0
- package/dist/main/Credentials.d.ts +22 -0
- package/dist/main/Credentials.js +45 -0
- package/dist/main/IamAwsProvider.d.ts +27 -0
- package/dist/main/IamAwsProvider.js +198 -0
- package/dist/main/errors.d.ts +82 -0
- package/dist/main/errors.js +138 -0
- package/dist/main/helpers.d.ts +156 -0
- package/dist/main/helpers.js +233 -0
- package/dist/main/internal/async.d.ts +9 -0
- package/dist/main/internal/async.js +24 -0
- package/dist/main/internal/callbackify.d.ts +1 -0
- package/dist/main/internal/callbackify.js +21 -0
- package/dist/main/internal/client.d.ts +394 -0
- package/dist/main/internal/client.js +3014 -0
- package/dist/main/internal/copy-conditions.d.ts +10 -0
- package/dist/main/internal/copy-conditions.js +31 -0
- package/dist/main/internal/extensions.d.ts +18 -0
- package/dist/main/internal/extensions.js +122 -0
- package/dist/main/internal/helper.d.ts +177 -0
- package/dist/main/internal/helper.js +608 -0
- package/dist/main/internal/join-host-port.d.ts +11 -0
- package/dist/main/internal/join-host-port.js +29 -0
- package/dist/main/internal/post-policy.d.ts +17 -0
- package/dist/main/internal/post-policy.js +107 -0
- package/dist/main/internal/request.d.ts +11 -0
- package/dist/main/internal/request.js +83 -0
- package/dist/main/internal/response.d.ts +8 -0
- package/dist/main/internal/response.js +24 -0
- package/dist/main/internal/s3-endpoints.d.ts +38 -0
- package/dist/main/internal/s3-endpoints.js +73 -0
- package/dist/main/internal/type.d.ts +482 -0
- package/dist/main/internal/type.js +42 -0
- package/dist/main/internal/xml-parser.d.ts +93 -0
- package/dist/main/internal/xml-parser.js +849 -0
- package/dist/main/notification.d.ts +58 -0
- package/dist/main/notification.js +230 -0
- package/dist/main/s3.d.ts +40 -0
- package/dist/main/s3.js +117 -0
- package/dist/main/signing.d.ts +5 -0
- package/dist/main/signing.js +269 -0
- package/package.json +146 -39
- package/src/AssumeRoleProvider.ts +262 -0
- package/src/CredentialProvider.ts +54 -0
- package/src/Credentials.ts +44 -0
- package/src/IamAwsProvider.ts +234 -0
- package/src/errors.ts +120 -0
- package/src/helpers.ts +354 -0
- package/src/internal/async.ts +14 -0
- package/src/internal/callbackify.ts +19 -0
- package/src/internal/client.ts +3412 -0
- package/src/internal/copy-conditions.ts +30 -0
- package/src/internal/extensions.ts +140 -0
- package/src/internal/helper.ts +606 -0
- package/src/internal/join-host-port.ts +23 -0
- package/src/internal/post-policy.ts +99 -0
- package/src/internal/request.ts +102 -0
- package/src/internal/response.ts +26 -0
- package/src/internal/s3-endpoints.ts +70 -0
- package/src/internal/type.ts +577 -0
- package/src/internal/xml-parser.ts +871 -0
- package/src/notification.ts +254 -0
- package/src/s3.ts +155 -0
- package/src/signing.ts +325 -0
- package/lib/index.js +0 -450
- package/lib/index.js.map +0 -7
- package/lib/perfTest.js +0 -91
- package/lib/perfTest.js.map +0 -7
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import type * as http from 'node:http'
|
|
2
|
+
import type stream from 'node:stream'
|
|
3
|
+
|
|
4
|
+
import crc32 from 'buffer-crc32'
|
|
5
|
+
import { XMLParser } from 'fast-xml-parser'
|
|
6
|
+
|
|
7
|
+
import * as errors from '../errors.ts'
|
|
8
|
+
import { SelectResults } from '../helpers.ts'
|
|
9
|
+
import { isObject, parseXml, readableStream, sanitizeETag, sanitizeObjectKey, sanitizeSize, toArray } from './helper.ts'
|
|
10
|
+
import { readAsString } from './response.ts'
|
|
11
|
+
import type {
|
|
12
|
+
BucketItemFromList,
|
|
13
|
+
BucketItemWithMetadata,
|
|
14
|
+
CloudFunctionConfigEntry,
|
|
15
|
+
CommonPrefix,
|
|
16
|
+
CopyObjectResultV1,
|
|
17
|
+
ListBucketResultV1,
|
|
18
|
+
ListObjectV2Res,
|
|
19
|
+
NotificationConfigResult,
|
|
20
|
+
ObjectInfo,
|
|
21
|
+
ObjectLockInfo,
|
|
22
|
+
ObjectRowEntry,
|
|
23
|
+
QueueConfigEntry,
|
|
24
|
+
ReplicationConfig,
|
|
25
|
+
Tag,
|
|
26
|
+
Tags,
|
|
27
|
+
TopicConfigEntry,
|
|
28
|
+
} from './type.ts'
|
|
29
|
+
import { RETENTION_VALIDITY_UNITS } from './type.ts'
|
|
30
|
+
|
|
31
|
+
// parse XML response for bucket region
|
|
32
|
+
export function parseBucketRegion(xml: string): string {
|
|
33
|
+
// return region information
|
|
34
|
+
return parseXml(xml).LocationConstraint
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const fxp = new XMLParser()
|
|
38
|
+
|
|
39
|
+
const fxpWithoutNumParser = new XMLParser({
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
numberParseOptions: {
|
|
42
|
+
skipLike: /./,
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// Parse XML and return information as Javascript types
|
|
47
|
+
// parse error XML response
|
|
48
|
+
export function parseError(xml: string, headerInfo: Record<string, unknown>) {
|
|
49
|
+
let xmlErr = {}
|
|
50
|
+
const xmlObj = fxp.parse(xml)
|
|
51
|
+
if (xmlObj.Error) {
|
|
52
|
+
xmlErr = xmlObj.Error
|
|
53
|
+
}
|
|
54
|
+
const e = new errors.S3Error() as unknown as Record<string, unknown>
|
|
55
|
+
Object.entries(xmlErr).forEach(([key, value]) => {
|
|
56
|
+
e[key.toLowerCase()] = value
|
|
57
|
+
})
|
|
58
|
+
Object.entries(headerInfo).forEach(([key, value]) => {
|
|
59
|
+
e[key] = value
|
|
60
|
+
})
|
|
61
|
+
return e
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Generates an Error object depending on http statusCode and XML body
|
|
65
|
+
export async function parseResponseError(response: http.IncomingMessage): Promise<Record<string, string>> {
|
|
66
|
+
const statusCode = response.statusCode
|
|
67
|
+
let code = '',
|
|
68
|
+
message = ''
|
|
69
|
+
if (statusCode === 301) {
|
|
70
|
+
code = 'MovedPermanently'
|
|
71
|
+
message = 'Moved Permanently'
|
|
72
|
+
} else if (statusCode === 307) {
|
|
73
|
+
code = 'TemporaryRedirect'
|
|
74
|
+
message = 'Are you using the correct endpoint URL?'
|
|
75
|
+
} else if (statusCode === 403) {
|
|
76
|
+
code = 'AccessDenied'
|
|
77
|
+
message = 'Valid and authorized credentials required'
|
|
78
|
+
} else if (statusCode === 404) {
|
|
79
|
+
code = 'NotFound'
|
|
80
|
+
message = 'Not Found'
|
|
81
|
+
} else if (statusCode === 405) {
|
|
82
|
+
code = 'MethodNotAllowed'
|
|
83
|
+
message = 'Method Not Allowed'
|
|
84
|
+
} else if (statusCode === 501) {
|
|
85
|
+
code = 'MethodNotAllowed'
|
|
86
|
+
message = 'Method Not Allowed'
|
|
87
|
+
} else if (statusCode === 503) {
|
|
88
|
+
code = 'SlowDown'
|
|
89
|
+
message = 'Please reduce your request rate.'
|
|
90
|
+
} else {
|
|
91
|
+
// Server-side error headers (protocol-level, sent by S3-compatible servers)
|
|
92
|
+
const hErrCode = (response.headers['x-s3-error-code'] ?? response.headers['x-minio-error-code']) as string
|
|
93
|
+
const hErrDesc = (response.headers['x-s3-error-desc'] ?? response.headers['x-minio-error-desc']) as string
|
|
94
|
+
|
|
95
|
+
if (hErrCode && hErrDesc) {
|
|
96
|
+
code = hErrCode
|
|
97
|
+
message = hErrDesc
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const headerInfo: Record<string, string | undefined | null> = {}
|
|
101
|
+
// A value created by S3 compatible server that uniquely identifies the request.
|
|
102
|
+
headerInfo.amzRequestid = response.headers['x-amz-request-id'] as string | undefined
|
|
103
|
+
// A special token that helps troubleshoot API replies and issues.
|
|
104
|
+
headerInfo.amzId2 = response.headers['x-amz-id-2'] as string | undefined
|
|
105
|
+
|
|
106
|
+
// Region where the bucket is located. This header is returned only
|
|
107
|
+
// in HEAD bucket and ListObjects response.
|
|
108
|
+
headerInfo.amzBucketRegion = response.headers['x-amz-bucket-region'] as string | undefined
|
|
109
|
+
|
|
110
|
+
const xmlString = await readAsString(response)
|
|
111
|
+
|
|
112
|
+
if (xmlString) {
|
|
113
|
+
throw parseError(xmlString, headerInfo)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Message should be instantiated for each S3Errors.
|
|
117
|
+
const e = new errors.S3Error(message, { cause: headerInfo })
|
|
118
|
+
// S3 Error code.
|
|
119
|
+
e.code = code
|
|
120
|
+
Object.entries(headerInfo).forEach(([key, value]) => {
|
|
121
|
+
// @ts-expect-error force set error properties
|
|
122
|
+
e[key] = value
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
throw e
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* parse XML response for list objects v2 with metadata in a bucket
|
|
130
|
+
*/
|
|
131
|
+
export function parseListObjectsV2WithMetadata(xml: string) {
|
|
132
|
+
const result: {
|
|
133
|
+
objects: Array<BucketItemWithMetadata>
|
|
134
|
+
isTruncated: boolean
|
|
135
|
+
nextContinuationToken: string
|
|
136
|
+
} = {
|
|
137
|
+
objects: [],
|
|
138
|
+
isTruncated: false,
|
|
139
|
+
nextContinuationToken: '',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let xmlobj = parseXml(xml)
|
|
143
|
+
if (!xmlobj.ListBucketResult) {
|
|
144
|
+
throw new errors.InvalidXMLError('Missing tag: "ListBucketResult"')
|
|
145
|
+
}
|
|
146
|
+
xmlobj = xmlobj.ListBucketResult
|
|
147
|
+
if (xmlobj.IsTruncated) {
|
|
148
|
+
result.isTruncated = xmlobj.IsTruncated
|
|
149
|
+
}
|
|
150
|
+
if (xmlobj.NextContinuationToken) {
|
|
151
|
+
result.nextContinuationToken = xmlobj.NextContinuationToken
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (xmlobj.Contents) {
|
|
155
|
+
toArray(xmlobj.Contents).forEach((content) => {
|
|
156
|
+
const name = sanitizeObjectKey(content.Key)
|
|
157
|
+
const lastModified = new Date(content.LastModified)
|
|
158
|
+
const etag = sanitizeETag(content.ETag)
|
|
159
|
+
const size = content.Size
|
|
160
|
+
|
|
161
|
+
let tags: Tags = {}
|
|
162
|
+
if (content.UserTags != null) {
|
|
163
|
+
toArray(content.UserTags.split('&')).forEach((tag) => {
|
|
164
|
+
const [key, value] = tag.split('=')
|
|
165
|
+
tags[key] = value
|
|
166
|
+
})
|
|
167
|
+
} else {
|
|
168
|
+
tags = {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let metadata
|
|
172
|
+
if (content.UserMetadata != null) {
|
|
173
|
+
metadata = toArray(content.UserMetadata)[0]
|
|
174
|
+
} else {
|
|
175
|
+
metadata = null
|
|
176
|
+
}
|
|
177
|
+
result.objects.push({ name, lastModified, etag, size, metadata, tags })
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (xmlobj.CommonPrefixes) {
|
|
182
|
+
toArray(xmlobj.CommonPrefixes).forEach((commonPrefix) => {
|
|
183
|
+
result.objects.push({ prefix: sanitizeObjectKey(toArray(commonPrefix.Prefix)[0]), size: 0 })
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
return result
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function parseListObjectsV2(xml: string): ListObjectV2Res {
|
|
190
|
+
const result: ListObjectV2Res = {
|
|
191
|
+
objects: [],
|
|
192
|
+
isTruncated: false,
|
|
193
|
+
nextContinuationToken: '',
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let xmlobj = parseXml(xml)
|
|
197
|
+
if (!xmlobj.ListBucketResult) {
|
|
198
|
+
throw new errors.InvalidXMLError('Missing tag: "ListBucketResult"')
|
|
199
|
+
}
|
|
200
|
+
xmlobj = xmlobj.ListBucketResult
|
|
201
|
+
if (xmlobj.IsTruncated) {
|
|
202
|
+
result.isTruncated = xmlobj.IsTruncated
|
|
203
|
+
}
|
|
204
|
+
if (xmlobj.NextContinuationToken) {
|
|
205
|
+
result.nextContinuationToken = xmlobj.NextContinuationToken
|
|
206
|
+
}
|
|
207
|
+
if (xmlobj.Contents) {
|
|
208
|
+
toArray(xmlobj.Contents).forEach((content) => {
|
|
209
|
+
const name = sanitizeObjectKey(toArray(content.Key)[0])
|
|
210
|
+
const lastModified = new Date(content.LastModified)
|
|
211
|
+
const etag = sanitizeETag(content.ETag)
|
|
212
|
+
const size = content.Size
|
|
213
|
+
result.objects.push({ name, lastModified, etag, size })
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
if (xmlobj.CommonPrefixes) {
|
|
217
|
+
toArray(xmlobj.CommonPrefixes).forEach((commonPrefix) => {
|
|
218
|
+
result.objects.push({ prefix: sanitizeObjectKey(toArray(commonPrefix.Prefix)[0]), size: 0 })
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
return result
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function parseBucketNotification(xml: string): NotificationConfigResult {
|
|
225
|
+
const result: NotificationConfigResult = {
|
|
226
|
+
TopicConfiguration: [],
|
|
227
|
+
QueueConfiguration: [],
|
|
228
|
+
CloudFunctionConfiguration: [],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const genEvents = (events: unknown): string[] => {
|
|
232
|
+
if (!events) {
|
|
233
|
+
return []
|
|
234
|
+
}
|
|
235
|
+
return toArray(events) as string[]
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const genFilterRules = (filters: unknown): { Name: string; Value: string }[] => {
|
|
239
|
+
const rules: { Name: string; Value: string }[] = []
|
|
240
|
+
if (!filters) {
|
|
241
|
+
return rules
|
|
242
|
+
}
|
|
243
|
+
const filterArr = toArray(filters) as Record<string, unknown>[]
|
|
244
|
+
if (filterArr[0]?.S3Key) {
|
|
245
|
+
const s3KeyArr = toArray((filterArr[0] as Record<string, unknown>).S3Key) as Record<string, unknown>[]
|
|
246
|
+
if (s3KeyArr[0]?.FilterRule) {
|
|
247
|
+
toArray(s3KeyArr[0].FilterRule).forEach((rule: unknown) => {
|
|
248
|
+
const r = rule as Record<string, unknown>
|
|
249
|
+
const Name = toArray(r.Name)[0] as string
|
|
250
|
+
const Value = toArray(r.Value)[0] as string
|
|
251
|
+
rules.push({ Name, Value })
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return rules
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let xmlobj = parseXml(xml)
|
|
259
|
+
xmlobj = xmlobj.NotificationConfiguration
|
|
260
|
+
|
|
261
|
+
if (xmlobj.TopicConfiguration) {
|
|
262
|
+
toArray(xmlobj.TopicConfiguration).forEach((config: Record<string, unknown>) => {
|
|
263
|
+
const Id = toArray(config.Id)[0] as string
|
|
264
|
+
const Topic = toArray(config.Topic)[0] as string
|
|
265
|
+
const Event = genEvents(config.Event)
|
|
266
|
+
const Filter = genFilterRules(config.Filter)
|
|
267
|
+
result.TopicConfiguration.push({ Id, Topic, Event, Filter } as TopicConfigEntry)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
if (xmlobj.QueueConfiguration) {
|
|
271
|
+
toArray(xmlobj.QueueConfiguration).forEach((config: Record<string, unknown>) => {
|
|
272
|
+
const Id = toArray(config.Id)[0] as string
|
|
273
|
+
const Queue = toArray(config.Queue)[0] as string
|
|
274
|
+
const Event = genEvents(config.Event)
|
|
275
|
+
const Filter = genFilterRules(config.Filter)
|
|
276
|
+
result.QueueConfiguration.push({ Id, Queue, Event, Filter } as QueueConfigEntry)
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
if (xmlobj.CloudFunctionConfiguration) {
|
|
280
|
+
toArray(xmlobj.CloudFunctionConfiguration).forEach((config: Record<string, unknown>) => {
|
|
281
|
+
const Id = toArray(config.Id)[0] as string
|
|
282
|
+
const CloudFunction = toArray(config.CloudFunction)[0] as string
|
|
283
|
+
const Event = genEvents(config.Event)
|
|
284
|
+
const Filter = genFilterRules(config.Filter)
|
|
285
|
+
result.CloudFunctionConfiguration.push({ Id, CloudFunction, Event, Filter } as CloudFunctionConfigEntry)
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return result
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export type UploadedPart = {
|
|
293
|
+
part: number
|
|
294
|
+
lastModified?: Date
|
|
295
|
+
etag: string
|
|
296
|
+
size: number
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// parse XML response for list parts of an in progress multipart upload
|
|
300
|
+
export function parseListParts(xml: string): {
|
|
301
|
+
isTruncated: boolean
|
|
302
|
+
marker: number
|
|
303
|
+
parts: UploadedPart[]
|
|
304
|
+
} {
|
|
305
|
+
let xmlobj = parseXml(xml)
|
|
306
|
+
const result: {
|
|
307
|
+
isTruncated: boolean
|
|
308
|
+
marker: number
|
|
309
|
+
parts: UploadedPart[]
|
|
310
|
+
} = {
|
|
311
|
+
isTruncated: false,
|
|
312
|
+
parts: [],
|
|
313
|
+
marker: 0,
|
|
314
|
+
}
|
|
315
|
+
if (!xmlobj.ListPartsResult) {
|
|
316
|
+
throw new errors.InvalidXMLError('Missing tag: "ListPartsResult"')
|
|
317
|
+
}
|
|
318
|
+
xmlobj = xmlobj.ListPartsResult
|
|
319
|
+
if (xmlobj.IsTruncated) {
|
|
320
|
+
result.isTruncated = xmlobj.IsTruncated
|
|
321
|
+
}
|
|
322
|
+
if (xmlobj.NextPartNumberMarker) {
|
|
323
|
+
result.marker = toArray(xmlobj.NextPartNumberMarker)[0] || ''
|
|
324
|
+
}
|
|
325
|
+
if (xmlobj.Part) {
|
|
326
|
+
toArray(xmlobj.Part).forEach((p) => {
|
|
327
|
+
const part = parseInt(toArray(p.PartNumber)[0], 10)
|
|
328
|
+
const lastModified = new Date(p.LastModified)
|
|
329
|
+
const etag = p.ETag.replace(/^"/g, '')
|
|
330
|
+
.replace(/"$/g, '')
|
|
331
|
+
.replace(/^"/g, '')
|
|
332
|
+
.replace(/"$/g, '')
|
|
333
|
+
.replace(/^"/g, '')
|
|
334
|
+
.replace(/"$/g, '')
|
|
335
|
+
result.parts.push({ part, lastModified, etag, size: parseInt(p.Size, 10) })
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
return result
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function parseListBucket(xml: string): BucketItemFromList[] {
|
|
342
|
+
let result: BucketItemFromList[] = []
|
|
343
|
+
const listBucketResultParser = new XMLParser({
|
|
344
|
+
parseTagValue: true, // Enable parsing of values
|
|
345
|
+
numberParseOptions: {
|
|
346
|
+
leadingZeros: false, // Disable number parsing for values with leading zeros
|
|
347
|
+
hex: false, // Disable hex number parsing - Invalid bucket name
|
|
348
|
+
skipLike: /^[0-9]+$/, // Skip number parsing if the value consists entirely of digits
|
|
349
|
+
},
|
|
350
|
+
tagValueProcessor: (tagName, tagValue = '') => {
|
|
351
|
+
// Ensure that the Name tag is always treated as a string
|
|
352
|
+
if (tagName === 'Name') {
|
|
353
|
+
return tagValue.toString()
|
|
354
|
+
}
|
|
355
|
+
return tagValue
|
|
356
|
+
},
|
|
357
|
+
ignoreAttributes: false, // Ensure that all attributes are parsed
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
const parsedXmlRes = listBucketResultParser.parse(xml)
|
|
361
|
+
|
|
362
|
+
if (!parsedXmlRes.ListAllMyBucketsResult) {
|
|
363
|
+
throw new errors.InvalidXMLError('Missing tag: "ListAllMyBucketsResult"')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { ListAllMyBucketsResult: { Buckets = {} } = {} } = parsedXmlRes
|
|
367
|
+
|
|
368
|
+
if (Buckets.Bucket) {
|
|
369
|
+
result = toArray(Buckets.Bucket).map((bucket = {}) => {
|
|
370
|
+
const { Name: bucketName, CreationDate } = bucket
|
|
371
|
+
const creationDate = new Date(CreationDate)
|
|
372
|
+
|
|
373
|
+
return { name: bucketName, creationDate }
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return result
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function parseInitiateMultipart(xml: string): string {
|
|
381
|
+
let xmlobj = parseXml(xml)
|
|
382
|
+
|
|
383
|
+
if (!xmlobj.InitiateMultipartUploadResult) {
|
|
384
|
+
throw new errors.InvalidXMLError('Missing tag: "InitiateMultipartUploadResult"')
|
|
385
|
+
}
|
|
386
|
+
xmlobj = xmlobj.InitiateMultipartUploadResult
|
|
387
|
+
|
|
388
|
+
if (xmlobj.UploadId) {
|
|
389
|
+
return xmlobj.UploadId
|
|
390
|
+
}
|
|
391
|
+
throw new errors.InvalidXMLError('Missing tag: "UploadId"')
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function parseReplicationConfig(xml: string): ReplicationConfig {
|
|
395
|
+
const xmlObj = parseXml(xml)
|
|
396
|
+
const { Role, Rule } = xmlObj.ReplicationConfiguration
|
|
397
|
+
return {
|
|
398
|
+
ReplicationConfiguration: {
|
|
399
|
+
role: Role,
|
|
400
|
+
rules: toArray(Rule),
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function parseObjectLegalHoldConfig(xml: string) {
|
|
406
|
+
const xmlObj = parseXml(xml)
|
|
407
|
+
return xmlObj.LegalHold
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function parseTagging(xml: string) {
|
|
411
|
+
const xmlObj = parseXml(xml)
|
|
412
|
+
let result: Tag[] = []
|
|
413
|
+
if (xmlObj.Tagging && xmlObj.Tagging.TagSet && xmlObj.Tagging.TagSet.Tag) {
|
|
414
|
+
const tagResult: Tag = xmlObj.Tagging.TagSet.Tag
|
|
415
|
+
// if it is a single tag convert into an array so that the return value is always an array.
|
|
416
|
+
if (Array.isArray(tagResult)) {
|
|
417
|
+
result = [...tagResult]
|
|
418
|
+
} else {
|
|
419
|
+
result.push(tagResult)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return result
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// parse XML response when a multipart upload is completed
|
|
426
|
+
export function parseCompleteMultipart(xml: string) {
|
|
427
|
+
const xmlobj = parseXml(xml).CompleteMultipartUploadResult
|
|
428
|
+
if (xmlobj.Location) {
|
|
429
|
+
const location = toArray(xmlobj.Location)[0]
|
|
430
|
+
const bucket = toArray(xmlobj.Bucket)[0]
|
|
431
|
+
const key = xmlobj.Key
|
|
432
|
+
const etag = xmlobj.ETag.replace(/^"/g, '')
|
|
433
|
+
.replace(/"$/g, '')
|
|
434
|
+
.replace(/^"/g, '')
|
|
435
|
+
.replace(/"$/g, '')
|
|
436
|
+
.replace(/^"/g, '')
|
|
437
|
+
.replace(/"$/g, '')
|
|
438
|
+
|
|
439
|
+
return { location, bucket, key, etag }
|
|
440
|
+
}
|
|
441
|
+
// Complete Multipart can return XML Error after a 200 OK response
|
|
442
|
+
if (xmlobj.Code && xmlobj.Message) {
|
|
443
|
+
const errCode = toArray(xmlobj.Code)[0]
|
|
444
|
+
const errMessage = toArray(xmlobj.Message)[0]
|
|
445
|
+
return { errCode, errMessage }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
type UploadID = string
|
|
450
|
+
|
|
451
|
+
export type ListMultipartResult = {
|
|
452
|
+
uploads: {
|
|
453
|
+
key: string
|
|
454
|
+
uploadId: UploadID
|
|
455
|
+
initiator?: { id: string; displayName: string }
|
|
456
|
+
owner?: { id: string; displayName: string }
|
|
457
|
+
storageClass: unknown
|
|
458
|
+
initiated: Date
|
|
459
|
+
}[]
|
|
460
|
+
prefixes: {
|
|
461
|
+
prefix: string
|
|
462
|
+
}[]
|
|
463
|
+
isTruncated: boolean
|
|
464
|
+
nextKeyMarker: string
|
|
465
|
+
nextUploadIdMarker: string
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// parse XML response for listing in-progress multipart uploads
|
|
469
|
+
export function parseListMultipart(xml: string): ListMultipartResult {
|
|
470
|
+
const result: ListMultipartResult = {
|
|
471
|
+
prefixes: [],
|
|
472
|
+
uploads: [],
|
|
473
|
+
isTruncated: false,
|
|
474
|
+
nextKeyMarker: '',
|
|
475
|
+
nextUploadIdMarker: '',
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let xmlobj = parseXml(xml)
|
|
479
|
+
|
|
480
|
+
if (!xmlobj.ListMultipartUploadsResult) {
|
|
481
|
+
throw new errors.InvalidXMLError('Missing tag: "ListMultipartUploadsResult"')
|
|
482
|
+
}
|
|
483
|
+
xmlobj = xmlobj.ListMultipartUploadsResult
|
|
484
|
+
if (xmlobj.IsTruncated) {
|
|
485
|
+
result.isTruncated = xmlobj.IsTruncated
|
|
486
|
+
}
|
|
487
|
+
if (xmlobj.NextKeyMarker) {
|
|
488
|
+
result.nextKeyMarker = xmlobj.NextKeyMarker
|
|
489
|
+
}
|
|
490
|
+
if (xmlobj.NextUploadIdMarker) {
|
|
491
|
+
result.nextUploadIdMarker = xmlobj.nextUploadIdMarker || ''
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (xmlobj.CommonPrefixes) {
|
|
495
|
+
toArray(xmlobj.CommonPrefixes).forEach((prefix) => {
|
|
496
|
+
// @ts-expect-error index check
|
|
497
|
+
result.prefixes.push({ prefix: sanitizeObjectKey(toArray<string>(prefix.Prefix)[0]) })
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (xmlobj.Upload) {
|
|
502
|
+
toArray(xmlobj.Upload).forEach((upload) => {
|
|
503
|
+
const uploadItem: ListMultipartResult['uploads'][number] = {
|
|
504
|
+
key: upload.Key,
|
|
505
|
+
uploadId: upload.UploadId,
|
|
506
|
+
storageClass: upload.StorageClass,
|
|
507
|
+
initiated: new Date(upload.Initiated),
|
|
508
|
+
}
|
|
509
|
+
if (upload.Initiator) {
|
|
510
|
+
uploadItem.initiator = { id: upload.Initiator.ID, displayName: upload.Initiator.DisplayName }
|
|
511
|
+
}
|
|
512
|
+
if (upload.Owner) {
|
|
513
|
+
uploadItem.owner = { id: upload.Owner.ID, displayName: upload.Owner.DisplayName }
|
|
514
|
+
}
|
|
515
|
+
result.uploads.push(uploadItem)
|
|
516
|
+
})
|
|
517
|
+
}
|
|
518
|
+
return result
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function parseObjectLockConfig(xml: string): ObjectLockInfo {
|
|
522
|
+
const xmlObj = parseXml(xml)
|
|
523
|
+
let lockConfigResult = {} as ObjectLockInfo
|
|
524
|
+
if (xmlObj.ObjectLockConfiguration) {
|
|
525
|
+
lockConfigResult = {
|
|
526
|
+
objectLockEnabled: xmlObj.ObjectLockConfiguration.ObjectLockEnabled,
|
|
527
|
+
} as ObjectLockInfo
|
|
528
|
+
let retentionResp
|
|
529
|
+
if (
|
|
530
|
+
xmlObj.ObjectLockConfiguration &&
|
|
531
|
+
xmlObj.ObjectLockConfiguration.Rule &&
|
|
532
|
+
xmlObj.ObjectLockConfiguration.Rule.DefaultRetention
|
|
533
|
+
) {
|
|
534
|
+
retentionResp = xmlObj.ObjectLockConfiguration.Rule.DefaultRetention || {}
|
|
535
|
+
lockConfigResult.mode = retentionResp.Mode
|
|
536
|
+
}
|
|
537
|
+
if (retentionResp) {
|
|
538
|
+
const isUnitYears = retentionResp.Years
|
|
539
|
+
if (isUnitYears) {
|
|
540
|
+
lockConfigResult.validity = isUnitYears
|
|
541
|
+
lockConfigResult.unit = RETENTION_VALIDITY_UNITS.YEARS
|
|
542
|
+
} else {
|
|
543
|
+
lockConfigResult.validity = retentionResp.Days
|
|
544
|
+
lockConfigResult.unit = RETENTION_VALIDITY_UNITS.DAYS
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return lockConfigResult
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function parseBucketVersioningConfig(xml: string) {
|
|
553
|
+
const xmlObj = parseXml(xml)
|
|
554
|
+
return xmlObj.VersioningConfiguration
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Used only in selectObjectContent API.
|
|
558
|
+
// extractHeaderType extracts the first half of the header message, the header type.
|
|
559
|
+
function extractHeaderType(stream: stream.Readable): string | undefined {
|
|
560
|
+
const headerNameLen = Buffer.from(stream.read(1)).readUInt8()
|
|
561
|
+
const headerNameWithSeparator = Buffer.from(stream.read(headerNameLen)).toString()
|
|
562
|
+
const splitBySeparator = (headerNameWithSeparator || '').split(':')
|
|
563
|
+
return splitBySeparator.length >= 1 ? splitBySeparator[1] : ''
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function extractHeaderValue(stream: stream.Readable) {
|
|
567
|
+
const bodyLen = Buffer.from(stream.read(2)).readUInt16BE()
|
|
568
|
+
return Buffer.from(stream.read(bodyLen)).toString()
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export function parseSelectObjectContentResponse(res: Buffer) {
|
|
572
|
+
const selectResults = new SelectResults({}) // will be returned
|
|
573
|
+
|
|
574
|
+
const responseStream = readableStream(res) // convert byte array to a readable responseStream
|
|
575
|
+
// @ts-ignore
|
|
576
|
+
while (responseStream._readableState.length) {
|
|
577
|
+
// Top level responseStream read tracker.
|
|
578
|
+
let msgCrcAccumulator // accumulate from start of the message till the message crc start.
|
|
579
|
+
|
|
580
|
+
const totalByteLengthBuffer = Buffer.from(responseStream.read(4))
|
|
581
|
+
msgCrcAccumulator = crc32(totalByteLengthBuffer)
|
|
582
|
+
|
|
583
|
+
const headerBytesBuffer = Buffer.from(responseStream.read(4))
|
|
584
|
+
msgCrcAccumulator = crc32(headerBytesBuffer, msgCrcAccumulator)
|
|
585
|
+
|
|
586
|
+
const calculatedPreludeCrc = msgCrcAccumulator.readInt32BE() // use it to check if any CRC mismatch in header itself.
|
|
587
|
+
|
|
588
|
+
const preludeCrcBuffer = Buffer.from(responseStream.read(4)) // read 4 bytes i.e 4+4 =8 + 4 = 12 ( prelude + prelude crc)
|
|
589
|
+
msgCrcAccumulator = crc32(preludeCrcBuffer, msgCrcAccumulator)
|
|
590
|
+
|
|
591
|
+
const totalMsgLength = totalByteLengthBuffer.readInt32BE()
|
|
592
|
+
const headerLength = headerBytesBuffer.readInt32BE()
|
|
593
|
+
const preludeCrcByteValue = preludeCrcBuffer.readInt32BE()
|
|
594
|
+
|
|
595
|
+
if (preludeCrcByteValue !== calculatedPreludeCrc) {
|
|
596
|
+
// Handle Header CRC mismatch Error
|
|
597
|
+
throw new Error(
|
|
598
|
+
`Header Checksum Mismatch, Prelude CRC of ${preludeCrcByteValue} does not equal expected CRC of ${calculatedPreludeCrc}`,
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const headers: Record<string, unknown> = {}
|
|
603
|
+
if (headerLength > 0) {
|
|
604
|
+
const headerBytes = Buffer.from(responseStream.read(headerLength))
|
|
605
|
+
msgCrcAccumulator = crc32(headerBytes, msgCrcAccumulator)
|
|
606
|
+
const headerReaderStream = readableStream(headerBytes)
|
|
607
|
+
// @ts-ignore
|
|
608
|
+
while (headerReaderStream._readableState.length) {
|
|
609
|
+
const headerTypeName = extractHeaderType(headerReaderStream)
|
|
610
|
+
headerReaderStream.read(1) // just read and ignore it.
|
|
611
|
+
if (headerTypeName) {
|
|
612
|
+
headers[headerTypeName] = extractHeaderValue(headerReaderStream)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
let payloadStream
|
|
618
|
+
const payLoadLength = totalMsgLength - headerLength - 16
|
|
619
|
+
if (payLoadLength > 0) {
|
|
620
|
+
const payLoadBuffer = Buffer.from(responseStream.read(payLoadLength))
|
|
621
|
+
msgCrcAccumulator = crc32(payLoadBuffer, msgCrcAccumulator)
|
|
622
|
+
// read the checksum early and detect any mismatch so we can avoid unnecessary further processing.
|
|
623
|
+
const messageCrcByteValue = Buffer.from(responseStream.read(4)).readInt32BE()
|
|
624
|
+
const calculatedCrc = msgCrcAccumulator.readInt32BE()
|
|
625
|
+
// Handle message CRC Error
|
|
626
|
+
if (messageCrcByteValue !== calculatedCrc) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
`Message Checksum Mismatch, Message CRC of ${messageCrcByteValue} does not equal expected CRC of ${calculatedCrc}`,
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
payloadStream = readableStream(payLoadBuffer)
|
|
632
|
+
}
|
|
633
|
+
const messageType = headers['message-type']
|
|
634
|
+
|
|
635
|
+
switch (messageType) {
|
|
636
|
+
case 'error': {
|
|
637
|
+
const errorMessage = headers['error-code'] + ':"' + headers['error-message'] + '"'
|
|
638
|
+
throw new Error(errorMessage)
|
|
639
|
+
}
|
|
640
|
+
case 'event': {
|
|
641
|
+
const contentType = headers['content-type']
|
|
642
|
+
const eventType = headers['event-type']
|
|
643
|
+
|
|
644
|
+
switch (eventType) {
|
|
645
|
+
case 'End': {
|
|
646
|
+
selectResults.setResponse(res)
|
|
647
|
+
return selectResults
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
case 'Records': {
|
|
651
|
+
const readData = payloadStream?.read(payLoadLength)
|
|
652
|
+
selectResults.setRecords(readData)
|
|
653
|
+
break
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
case 'Progress':
|
|
657
|
+
{
|
|
658
|
+
switch (contentType) {
|
|
659
|
+
case 'text/xml': {
|
|
660
|
+
const progressData = payloadStream?.read(payLoadLength)
|
|
661
|
+
selectResults.setProgress(progressData.toString())
|
|
662
|
+
break
|
|
663
|
+
}
|
|
664
|
+
default: {
|
|
665
|
+
const errorMessage = `Unexpected content-type ${contentType} sent for event-type Progress`
|
|
666
|
+
throw new Error(errorMessage)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
break
|
|
671
|
+
case 'Stats':
|
|
672
|
+
{
|
|
673
|
+
switch (contentType) {
|
|
674
|
+
case 'text/xml': {
|
|
675
|
+
const statsData = payloadStream?.read(payLoadLength)
|
|
676
|
+
selectResults.setStats(statsData.toString())
|
|
677
|
+
break
|
|
678
|
+
}
|
|
679
|
+
default: {
|
|
680
|
+
const errorMessage = `Unexpected content-type ${contentType} sent for event-type Stats`
|
|
681
|
+
throw new Error(errorMessage)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
break
|
|
686
|
+
default: {
|
|
687
|
+
// Continuation message: Not sure if it is supported. did not find a reference or any message in response.
|
|
688
|
+
// It does not have a payload.
|
|
689
|
+
const warningMessage = `Un implemented event detected ${messageType}.`
|
|
690
|
+
// eslint-disable-next-line no-console
|
|
691
|
+
console.warn(warningMessage)
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function parseLifecycleConfig(xml: string) {
|
|
700
|
+
const xmlObj = parseXml(xml)
|
|
701
|
+
return xmlObj.LifecycleConfiguration
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export function parseBucketEncryptionConfig(xml: string) {
|
|
705
|
+
return parseXml(xml)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export function parseObjectRetentionConfig(xml: string) {
|
|
709
|
+
const xmlObj = parseXml(xml)
|
|
710
|
+
const retentionConfig = xmlObj.Retention
|
|
711
|
+
return {
|
|
712
|
+
mode: retentionConfig.Mode,
|
|
713
|
+
retainUntilDate: retentionConfig.RetainUntilDate,
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function removeObjectsParser(xml: string) {
|
|
718
|
+
const xmlObj = parseXml(xml)
|
|
719
|
+
if (xmlObj.DeleteResult && xmlObj.DeleteResult.Error) {
|
|
720
|
+
// return errors as array always. as the response is object in case of single object passed in removeObjects
|
|
721
|
+
return toArray(xmlObj.DeleteResult.Error)
|
|
722
|
+
}
|
|
723
|
+
return []
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// parse XML response for copy object
|
|
727
|
+
export function parseCopyObject(xml: string): CopyObjectResultV1 {
|
|
728
|
+
const result: CopyObjectResultV1 = {
|
|
729
|
+
etag: '',
|
|
730
|
+
lastModified: '',
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
let xmlobj = parseXml(xml)
|
|
734
|
+
if (!xmlobj.CopyObjectResult) {
|
|
735
|
+
throw new errors.InvalidXMLError('Missing tag: "CopyObjectResult"')
|
|
736
|
+
}
|
|
737
|
+
xmlobj = xmlobj.CopyObjectResult
|
|
738
|
+
if (xmlobj.ETag) {
|
|
739
|
+
result.etag = xmlobj.ETag.replace(/^"/g, '')
|
|
740
|
+
.replace(/"$/g, '')
|
|
741
|
+
.replace(/^"/g, '')
|
|
742
|
+
.replace(/"$/g, '')
|
|
743
|
+
.replace(/^"/g, '')
|
|
744
|
+
.replace(/"$/g, '')
|
|
745
|
+
}
|
|
746
|
+
if (xmlobj.LastModified) {
|
|
747
|
+
result.lastModified = new Date(xmlobj.LastModified)
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return result
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const formatObjInfo = (content: ObjectRowEntry, opts: { IsDeleteMarker?: boolean } = {}) => {
|
|
754
|
+
const { Key, LastModified, ETag, Size, VersionId, IsLatest } = content
|
|
755
|
+
|
|
756
|
+
if (!isObject(opts)) {
|
|
757
|
+
opts = {}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const name = sanitizeObjectKey(toArray(Key)[0] || '')
|
|
761
|
+
const lastModified = LastModified ? new Date(toArray(LastModified)[0] || '') : undefined
|
|
762
|
+
const etag = sanitizeETag(toArray(ETag)[0] || '')
|
|
763
|
+
const size = sanitizeSize(Size || '')
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
name,
|
|
767
|
+
lastModified,
|
|
768
|
+
etag,
|
|
769
|
+
size,
|
|
770
|
+
versionId: VersionId,
|
|
771
|
+
isLatest: IsLatest,
|
|
772
|
+
isDeleteMarker: opts.IsDeleteMarker ? opts.IsDeleteMarker : false,
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// parse XML response for list objects in a bucket
|
|
777
|
+
export function parseListObjects(xml: string) {
|
|
778
|
+
const result: {
|
|
779
|
+
objects: ObjectInfo[]
|
|
780
|
+
isTruncated?: boolean
|
|
781
|
+
nextMarker?: string
|
|
782
|
+
versionIdMarker?: string
|
|
783
|
+
keyMarker?: string
|
|
784
|
+
} = {
|
|
785
|
+
objects: [],
|
|
786
|
+
isTruncated: false,
|
|
787
|
+
nextMarker: undefined,
|
|
788
|
+
versionIdMarker: undefined,
|
|
789
|
+
keyMarker: undefined,
|
|
790
|
+
}
|
|
791
|
+
let isTruncated = false
|
|
792
|
+
let nextMarker
|
|
793
|
+
const xmlobj = fxpWithoutNumParser.parse(xml)
|
|
794
|
+
|
|
795
|
+
const parseCommonPrefixesEntity = (commonPrefixEntry: CommonPrefix[]) => {
|
|
796
|
+
if (commonPrefixEntry) {
|
|
797
|
+
toArray(commonPrefixEntry).forEach((commonPrefix) => {
|
|
798
|
+
result.objects.push({ prefix: sanitizeObjectKey(toArray(commonPrefix.Prefix)[0] || ''), size: 0 })
|
|
799
|
+
})
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const listBucketResult: ListBucketResultV1 = xmlobj.ListBucketResult
|
|
804
|
+
const listVersionsResult: ListBucketResultV1 = xmlobj.ListVersionsResult
|
|
805
|
+
|
|
806
|
+
if (listBucketResult) {
|
|
807
|
+
if (listBucketResult.IsTruncated) {
|
|
808
|
+
isTruncated = listBucketResult.IsTruncated
|
|
809
|
+
}
|
|
810
|
+
if (listBucketResult.Contents) {
|
|
811
|
+
toArray(listBucketResult.Contents).forEach((content) => {
|
|
812
|
+
const name = sanitizeObjectKey(toArray(content.Key)[0] || '')
|
|
813
|
+
const lastModified = new Date(toArray(content.LastModified)[0] || '')
|
|
814
|
+
const etag = sanitizeETag(toArray(content.ETag)[0] || '')
|
|
815
|
+
const size = sanitizeSize(content.Size || '')
|
|
816
|
+
result.objects.push({ name, lastModified, etag, size })
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (listBucketResult.Marker) {
|
|
821
|
+
nextMarker = listBucketResult.Marker
|
|
822
|
+
}
|
|
823
|
+
if (listBucketResult.NextMarker) {
|
|
824
|
+
nextMarker = listBucketResult.NextMarker
|
|
825
|
+
} else if (isTruncated && result.objects.length > 0) {
|
|
826
|
+
nextMarker = result.objects[result.objects.length - 1]?.name
|
|
827
|
+
}
|
|
828
|
+
if (listBucketResult.CommonPrefixes) {
|
|
829
|
+
parseCommonPrefixesEntity(listBucketResult.CommonPrefixes)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (listVersionsResult) {
|
|
834
|
+
if (listVersionsResult.IsTruncated) {
|
|
835
|
+
isTruncated = listVersionsResult.IsTruncated
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (listVersionsResult.Version) {
|
|
839
|
+
toArray(listVersionsResult.Version).forEach((content) => {
|
|
840
|
+
result.objects.push(formatObjInfo(content))
|
|
841
|
+
})
|
|
842
|
+
}
|
|
843
|
+
if (listVersionsResult.DeleteMarker) {
|
|
844
|
+
toArray(listVersionsResult.DeleteMarker).forEach((content) => {
|
|
845
|
+
result.objects.push(formatObjInfo(content, { IsDeleteMarker: true }))
|
|
846
|
+
})
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (listVersionsResult.NextKeyMarker) {
|
|
850
|
+
result.keyMarker = listVersionsResult.NextKeyMarker
|
|
851
|
+
}
|
|
852
|
+
if (listVersionsResult.NextVersionIdMarker) {
|
|
853
|
+
result.versionIdMarker = listVersionsResult.NextVersionIdMarker
|
|
854
|
+
}
|
|
855
|
+
if (listVersionsResult.CommonPrefixes) {
|
|
856
|
+
parseCommonPrefixesEntity(listVersionsResult.CommonPrefixes)
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
result.isTruncated = isTruncated
|
|
861
|
+
if (isTruncated) {
|
|
862
|
+
result.nextMarker = nextMarker
|
|
863
|
+
}
|
|
864
|
+
return result
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
export function uploadPartParser(xml: string) {
|
|
868
|
+
const xmlObj = parseXml(xml)
|
|
869
|
+
const respEl = xmlObj.CopyPartResult
|
|
870
|
+
return respEl
|
|
871
|
+
}
|