@uppy/aws-s3: wrong upload meta in upload-success event (#6234)

right now whenever you upload any file through multipart upload you'll
find empty body returned in `upload-success` event

cause : response from completeMultipartRequest returned in CamelCase
which was not parsed correctly

```xml
<?xml version="1.0" encoding="UTF-8"?>

<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>https://testbucketnewfix.s3.eu-north-1.amazonaws.com/13febe01-8d5b-4ed3-bda1-9620fc37c694-VSCode-darwin-universal.dmg</Location><Bucket>testbucketnewfix</Bucket><Key>13febe01-8d5b-4ed3-bda1-9620fc37c694-VSCode-darwin-universal.dmg</Key><ETag>"be4129821e06a893b453f1373995202b-49"</ETag><ChecksumCRC64NVME>Wjr7mcJOfU0=</ChecksumCRC64NVME><ChecksumType>FULL_OBJECT</ChecksumType></CompleteMultipartUploadResult>
```

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Prakash 2026-03-20 18:11:22 +05:30 committed by prakash
parent 50de6d1066
commit da46951771
No known key found for this signature in database
4 changed files with 27 additions and 39 deletions

View file

@ -621,25 +621,36 @@ class S3mini {
const parsed = U.parseXml(await res.text()) as Record<string, unknown>
if (parsed && typeof parsed === 'object') {
// Check for both cases
// Check for both cases (camelCase from our parser, PascalCase from S3)
const result =
parsed.completeMultipartUploadResult ||
parsed.CompleteMultipartUploadResult ||
parsed
if (result && typeof result === 'object') {
const resultObj = result as Record<string, unknown>
const r = result as Record<string, unknown>
// Handle ETag in all its variations
const etag = resultObj.ETag || resultObj.eTag || resultObj.etag
if (etag && typeof etag === 'string') {
return {
...resultObj,
etag: U.sanitizeETag(etag),
} as IT.CompleteMultipartUploadResult
// S3 returns PascalCase (Location, Bucket, Key, ETag).
// Normalize to lowercase for our type interface.
const resultLocation = (r.Location || r.location) as string | undefined
const resultBucket = (r.Bucket || r.bucket) as string | undefined
const resultKey = (r.Key || r.key) as string | undefined
const rawEtag = (r.ETag || r.eTag || r.etag) as string | undefined
if (!resultLocation || !resultKey) {
throw new Error(
`${C.ERROR_PREFIX}CompleteMultipartUpload response missing Location or Key: ${JSON.stringify(r)}`,
)
}
return result as IT.CompleteMultipartUploadResult
const etag = rawEtag ? U.sanitizeETag(rawEtag) : ''
return {
location: resultLocation,
bucket: resultBucket ?? '',
key: resultKey,
etag,
} satisfies IT.CompleteMultipartUploadResult
}
}

View file

@ -4,7 +4,6 @@ export { S3mini } from './S3.js'
export type {
CompleteMultipartUploadResult,
ErrorWithCode,
ListMultipartUploadResponse,
S3Config,
UploadPart,
} from './types.js'

View file

@ -85,30 +85,6 @@ export interface CompleteMultipartUploadResult {
bucket: string
key: string
etag: string
eTag: string // for backward compatibility
ETag: string // for backward compatibility
}
export interface ListMultipartUploadSuccess {
listMultipartUploadsResult: {
bucket: string
key: string
uploadId: string
size?: number
mtime?: Date
etag?: string
eTag?: string // for backward compatibility
parts: UploadPart[]
isTruncated: boolean
uploads: UploadPart[]
}
}
export interface MultipartUploadError {
error: {
code: string
message: string
}
}
export interface ErrorWithCode {
@ -116,10 +92,6 @@ export interface ErrorWithCode {
cause?: { code?: string }
}
export type ListMultipartUploadResponse =
| ListMultipartUploadSuccess
| MultipartUploadError
export type HttpMethod = 'POST' | 'GET' | 'HEAD' | 'PUT' | 'DELETE'
export type XmlValue = string | XmlMap | boolean | number | null

View file

@ -98,6 +98,9 @@ const minioSpecific = (bucket) => {
uploaded,
])
expect(result.etag).toBeDefined()
expect(result.location).toBeDefined()
expect(result.location).toContain(key)
expect(result.key).toBe(key)
await s3client.deleteObject(key)
})
@ -223,6 +226,9 @@ const minioSpecific = (bucket) => {
const result = await s3.completeMultipartUpload(key, uploadId, [uploaded])
expect(result.etag).toBeDefined()
expect(result.location).toBeDefined()
expect(result.location).toContain(key)
expect(result.key).toBe(key)
await s3.deleteObject(key)
})