14
14
* limitations under the License.
15
15
*/
16
16
17
- import xml2js from 'xml2js '
17
+ import fxp from 'fast-xml-parser '
18
18
import _ from 'lodash'
19
19
import * as errors from './errors.js'
20
20
21
- var options = { // options passed to xml2js parser
22
- explicitRoot : false , // return the root node in the resulting object?
23
- ignoreAttrs : true , // ignore attributes, only create text nodes
24
- }
25
-
26
21
var parseXml = ( xml ) => {
27
22
var result = null
28
- var error = null
29
-
30
- var parser = new xml2js . Parser ( options )
31
- parser . parseString ( xml , function ( e , r ) {
32
- error = e
33
- result = r
34
- } )
35
-
36
- if ( error ) {
37
- throw new Error ( 'XML parse error' )
23
+ result = fxp . parse ( xml )
24
+ if ( result . Error ) {
25
+ throw result . Error
38
26
}
27
+
39
28
return result
40
29
}
41
30
42
31
// Parse XML and return information as Javascript types
43
32
44
33
// parse error XML response
45
34
export function parseError ( xml , headerInfo ) {
46
- var xmlError = { }
47
- var xmlobj = parseXml ( xml )
48
- var message
49
- _ . each ( xmlobj , ( n , key ) => {
50
- if ( key === 'Message' ) {
51
- message = xmlobj [ key ] [ 0 ]
52
- return
53
- }
54
- xmlError [ key . toLowerCase ( ) ] = xmlobj [ key ] [ 0 ]
55
- } )
56
- var e = new errors . S3Error ( message )
57
- _ . each ( xmlError , ( value , key ) => {
58
- e [ key ] = value
35
+ var xmlErr = { }
36
+ var xmlObj = fxp . parse ( xml )
37
+ if ( xmlObj . Error ) {
38
+ xmlErr = xmlObj . Error
39
+ }
40
+
41
+ var e = new errors . S3Error ( )
42
+ _ . each ( xmlErr , ( value , key ) => {
43
+ e [ key . toLowerCase ( ) ] = value
59
44
} )
45
+
60
46
_ . each ( headerInfo , ( value , key ) => {
61
47
e [ key ] = value
62
48
} )
@@ -69,11 +55,16 @@ export function parseCopyObject(xml) {
69
55
etag : "" ,
70
56
lastModified : ""
71
57
}
58
+
72
59
var xmlobj = parseXml ( xml )
73
- if ( xmlobj . ETag ) result . etag = xmlobj . ETag [ 0 ] . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
60
+ if ( ! xmlobj . CopyObjectResult ) {
61
+ throw new errors . InvalidXMLError ( 'Missing tag: "CopyObjectResult"' )
62
+ }
63
+ xmlobj = xmlobj . CopyObjectResult
64
+ if ( xmlobj . ETag ) result . etag = xmlobj . ETag . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
74
65
. replace ( / ^ & q u o t ; / g, '' ) . replace ( / & q u o t ; $ / g, '' )
75
- . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / ^ & # 3 4 ; $ / g, '' )
76
- if ( xmlobj . LastModified ) result . lastModified = new Date ( xmlobj . LastModified [ 0 ] )
66
+ . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / & # 3 4 ; $ / g, '' )
67
+ if ( xmlobj . LastModified ) result . lastModified = new Date ( xmlobj . LastModified )
77
68
78
69
return result
79
70
}
@@ -85,32 +76,54 @@ export function parseListMultipart(xml) {
85
76
prefixes : [ ] ,
86
77
isTruncated : false
87
78
}
88
- var xmlobj = parseXml ( xml )
89
- if ( xmlobj . IsTruncated && xmlobj . IsTruncated [ 0 ] === 'true' ) result . isTruncated = true
90
- if ( xmlobj . NextKeyMarker ) result . nextKeyMarker = xmlobj . NextKeyMarker [ 0 ]
79
+
80
+ var xmlobj = parseXml ( xml )
81
+
82
+ if ( ! xmlobj . ListMultipartUploadsResult ) {
83
+ throw new errors . InvalidXMLError ( 'Missing tag: "ListMultipartUploadsResult"' )
84
+ }
85
+ xmlobj = xmlobj . ListMultipartUploadsResult
86
+ if ( xmlobj . IsTruncated && xmlobj . IsTruncated === 'true' ) result . isTruncated = true
87
+ if ( xmlobj . NextKeyMarker ) result . nextKeyMarker = xmlobj . NextKeyMarker
91
88
if ( xmlobj . NextUploadIdMarker ) result . nextUploadIdMarker = xmlobj . NextUploadIdMarker [ 0 ]
92
89
if ( xmlobj . CommonPrefixes ) xmlobj . CommonPrefixes . forEach ( prefix => {
93
90
result . prefixes . push ( { prefix : prefix [ 0 ] } )
94
91
} )
95
- if ( xmlobj . Upload ) xmlobj . Upload . forEach ( upload => {
96
- result . uploads . push ( {
97
- key : upload . Key [ 0 ] ,
98
- uploadId : upload . UploadId [ 0 ] ,
99
- initiated : new Date ( upload . Initiated [ 0 ] )
92
+ if ( xmlobj . Upload ) {
93
+ if ( ! Array . isArray ( xmlobj . Upload ) ) {
94
+ xmlobj . Upload = Array ( xmlobj . Upload )
95
+ }
96
+ xmlobj . Upload . forEach ( upload => {
97
+ var key = upload . Key
98
+ var uploadId = upload . UploadId
99
+ var initiator = { id : upload . Initiator . ID , displayName : upload . Initiator . DisplayName }
100
+ var owner = { id : upload . Owner . ID , displayName : upload . Owner . DisplayName }
101
+ var storageClass = upload . StorageClass
102
+ var initiated = new Date ( upload . Initiated )
103
+ result . uploads . push ( { key, uploadId, initiator, owner, storageClass, initiated} )
100
104
} )
101
- } )
105
+ }
102
106
return result
103
107
}
104
108
105
109
// parse XML response to list all the owned buckets
106
110
export function parseListBucket ( xml ) {
107
111
var result = [ ]
108
112
var xmlobj = parseXml ( xml )
113
+
114
+ if ( ! xmlobj . ListAllMyBucketsResult ) {
115
+ throw new errors . InvalidXMLError ( 'Missing tag: "ListAllMyBucketsResult"' )
116
+ }
117
+ xmlobj = xmlobj . ListAllMyBucketsResult
118
+
109
119
if ( xmlobj . Buckets ) {
110
- if ( xmlobj . Buckets [ 0 ] . Bucket ) {
111
- xmlobj . Buckets [ 0 ] . Bucket . forEach ( bucket => {
112
- var name = bucket . Name [ 0 ]
113
- var creationDate = new Date ( bucket . CreationDate [ 0 ] )
120
+ if ( xmlobj . Buckets . Bucket ) {
121
+ if ( ! Array . isArray ( xmlobj . Buckets . Bucket ) ) {
122
+ xmlobj . Buckets . Bucket = Array ( xmlobj . Buckets . Bucket )
123
+ }
124
+ xmlobj . Buckets . Bucket . forEach ( bucket => {
125
+ var name = bucket . Name
126
+ var creationDate = new Date ( bucket . CreationDate )
114
127
result . push ( { name, creationDate} )
115
128
} )
116
129
}
@@ -149,7 +162,6 @@ export function parseBucketNotification(xml) {
149
162
}
150
163
151
164
var xmlobj = parseXml ( xml )
152
-
153
165
// Parse all topic configurations in the xml
154
166
if ( xmlobj . TopicConfiguration ) {
155
167
xmlobj . TopicConfiguration . forEach ( config => {
@@ -186,7 +198,8 @@ export function parseBucketNotification(xml) {
186
198
187
199
// parse XML response for bucket region
188
200
export function parseBucketRegion ( xml ) {
189
- return parseXml ( xml )
201
+ // return region information
202
+ return parseXml ( xml ) . LocationConstraint
190
203
}
191
204
192
205
// parse XML response for list parts of an in progress multipart upload
@@ -197,15 +210,15 @@ export function parseListParts(xml) {
197
210
parts : [ ] ,
198
211
marker : undefined
199
212
}
200
- if ( xmlobj . IsTruncated && xmlobj . IsTruncated [ 0 ] === 'true' ) result . isTruncated = true
213
+ if ( xmlobj . IsTruncated && xmlobj . IsTruncated === 'true' ) result . isTruncated = true
201
214
if ( xmlobj . NextPartNumberMarker ) result . marker = + xmlobj . NextPartNumberMarker [ 0 ]
202
215
if ( xmlobj . Part ) {
203
216
xmlobj . Part . forEach ( p => {
204
217
var part = + p . PartNumber [ 0 ]
205
- var lastModified = new Date ( p . LastModified [ 0 ] )
206
- var etag = p . ETag [ 0 ] . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
218
+ var lastModified = new Date ( p . LastModified )
219
+ var etag = p . ETag . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
207
220
. replace ( / ^ & q u o t ; / g, '' ) . replace ( / & q u o t ; $ / g, '' )
208
- . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / ^ & # 3 4 ; $ / g, '' )
221
+ . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / & # 3 4 ; $ / g, '' )
209
222
result . parts . push ( { part, lastModified, etag} )
210
223
} )
211
224
}
@@ -214,21 +227,21 @@ export function parseListParts(xml) {
214
227
215
228
// parse XML response when a new multipart upload is initiated
216
229
export function parseInitiateMultipart ( xml ) {
217
- var xmlobj = parseXml ( xml )
218
- if ( xmlobj . UploadId ) return xmlobj . UploadId [ 0 ]
219
- throw new errors . InvalidXMLError ( 'UploadId missing in XML ' )
230
+ var xmlobj = parseXml ( xml ) . InitiateMultipartUploadResult
231
+ if ( xmlobj . UploadId ) return xmlobj . UploadId
232
+ throw new errors . InvalidXMLError ( 'Missing tag: "UploadId" ' )
220
233
}
221
234
222
235
// parse XML response when a multipart upload is completed
223
236
export function parseCompleteMultipart ( xml ) {
224
- var xmlobj = parseXml ( xml )
237
+ var xmlobj = parseXml ( xml ) . CompleteMultipartUploadResult
225
238
if ( xmlobj . Location ) {
226
239
var location = xmlobj . Location [ 0 ]
227
240
var bucket = xmlobj . Bucket [ 0 ]
228
- var key = xmlobj . Key [ 0 ]
229
- var etag = xmlobj . ETag [ 0 ] . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
241
+ var key = xmlobj . Key
242
+ var etag = xmlobj . ETag . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
230
243
. replace ( / ^ & q u o t ; / g, '' ) . replace ( / & q u o t ; $ / g, '' )
231
- . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / ^ & # 3 4 ; $ / g, '' )
244
+ . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / & # 3 4 ; $ / g, '' )
232
245
233
246
return { location, bucket, key, etag}
234
247
}
@@ -248,15 +261,23 @@ export function parseListObjects(xml) {
248
261
}
249
262
var nextMarker
250
263
var xmlobj = parseXml ( xml )
251
- if ( xmlobj . IsTruncated && xmlobj . IsTruncated [ 0 ] === 'true' ) result . isTruncated = true
264
+
265
+ if ( ! xmlobj . ListBucketResult ) {
266
+ throw new errors . InvalidXMLError ( 'Missing tag: "ListBucketResult"' )
267
+ }
268
+ xmlobj = xmlobj . ListBucketResult
269
+ if ( xmlobj . IsTruncated && xmlobj . IsTruncated === 'true' ) xmlobj . isTruncated = true
252
270
if ( xmlobj . Contents ) {
271
+ if ( ! Array . isArray ( xmlobj . Contents ) ) {
272
+ xmlobj . Contents = Array ( xmlobj . Contents )
273
+ }
253
274
xmlobj . Contents . forEach ( content => {
254
- var name = content . Key [ 0 ]
255
- var lastModified = new Date ( content . LastModified [ 0 ] )
256
- var etag = content . ETag [ 0 ] . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
275
+ var name = content . Key
276
+ var lastModified = new Date ( content . LastModified )
277
+ var etag = content . ETag . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
257
278
. replace ( / ^ & q u o t ; / g, '' ) . replace ( / & q u o t ; $ / g, '' )
258
- . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / ^ & # 3 4 ; $ / g, '' )
259
- var size = + content . Size [ 0 ]
279
+ . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / & # 3 4 ; $ / g, '' )
280
+ var size = + content . Size
260
281
result . objects . push ( { name, lastModified, etag, size} )
261
282
nextMarker = name
262
283
} )
@@ -281,17 +302,24 @@ export function parseListObjectsV2(xml) {
281
302
isTruncated : false
282
303
}
283
304
var xmlobj = parseXml ( xml )
284
- if ( xmlobj . IsTruncated && xmlobj . IsTruncated [ 0 ] === 'true' ) result . isTruncated = true
305
+ if ( ! xmlobj . ListBucketResult ) {
306
+ throw new errors . InvalidXMLError ( 'Missing tag: "ListBucketResult"' )
307
+ }
308
+ xmlobj = xmlobj . ListBucketResult
309
+ if ( xmlobj . IsTruncated && xmlobj . IsTruncated === 'true' ) result . isTruncated = true
285
310
if ( xmlobj . NextContinuationToken ) result . nextContinuationToken = xmlobj . NextContinuationToken [ 0 ]
286
311
287
312
if ( xmlobj . Contents ) {
313
+ if ( ! Array . isArray ( xmlobj . Contents ) ) {
314
+ xmlobj . Contents = Array ( xmlobj . Contents )
315
+ }
288
316
xmlobj . Contents . forEach ( content => {
289
- var name = content . Key [ 0 ]
290
- var lastModified = new Date ( content . LastModified [ 0 ] )
291
- var etag = content . ETag [ 0 ] . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
317
+ var name = content . Key
318
+ var lastModified = new Date ( content . LastModified )
319
+ var etag = content . ETag . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
292
320
. replace ( / ^ & q u o t ; / g, '' ) . replace ( / & q u o t ; $ / g, '' )
293
- . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / ^ & # 3 4 ; $ / g, '' )
294
- var size = + content . Size [ 0 ]
321
+ . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / & # 3 4 ; $ / g, '' )
322
+ var size = + content . Size
295
323
result . objects . push ( { name, lastModified, etag, size} )
296
324
} )
297
325
}
@@ -312,17 +340,24 @@ export function parseListObjectsV2WithMetadata(xml) {
312
340
isTruncated : false
313
341
}
314
342
var xmlobj = parseXml ( xml )
315
- if ( xmlobj . IsTruncated && xmlobj . IsTruncated [ 0 ] === 'true' ) result . isTruncated = true
343
+ if ( ! xmlobj . ListBucketResult ) {
344
+ throw new errors . InvalidXMLError ( 'Missing tag: "ListBucketResult"' )
345
+ }
346
+ xmlobj = xmlobj . ListBucketResult
347
+ if ( xmlobj . IsTruncated && xmlobj . IsTruncated === 'true' ) result . isTruncated = true
316
348
if ( xmlobj . NextContinuationToken ) result . nextContinuationToken = xmlobj . NextContinuationToken [ 0 ]
317
349
318
350
if ( xmlobj . Contents ) {
351
+ if ( ! Array . isArray ( xmlobj . Contents ) ) {
352
+ xmlobj . Contents = Array ( xmlobj . Contents )
353
+ }
319
354
xmlobj . Contents . forEach ( content => {
320
- var name = content . Key [ 0 ]
321
- var lastModified = new Date ( content . LastModified [ 0 ] )
322
- var etag = content . ETag [ 0 ] . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
355
+ var name = content . Key
356
+ var lastModified = new Date ( content . LastModified )
357
+ var etag = content . ETag . replace ( / ^ " / g, '' ) . replace ( / " $ / g, '' )
323
358
. replace ( / ^ & q u o t ; / g, '' ) . replace ( / & q u o t ; $ / g, '' )
324
- . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / ^ & # 3 4 ; $ / g, '' )
325
- var size = + content . Size [ 0 ]
359
+ . replace ( / ^ & # 3 4 ; / g, '' ) . replace ( / & # 3 4 ; $ / g, '' )
360
+ var size = + content . Size
326
361
var metadata
327
362
if ( content . UserMetadata != null ) {
328
363
metadata = content . UserMetadata [ 0 ]
0 commit comments