* Parses a ListObjectsV2 XML response into object entries plus pagination state. * * The request is always made with `encoding-type=url`, so the per-`Key` values * are percent-encoded in the XML (safe for the regex parser even when keys * contain XML-hostile bytes such as `&`, `<`, or ASCII cont
(xml: string)
| 402 | * it is used verbatim. |
| 403 | */ |
| 404 | function parseListResponse(xml: string): { |
| 405 | objects: S3ObjectEntry[] |
| 406 | isTruncated: boolean |
| 407 | nextContinuationToken?: string |
| 408 | } { |
| 409 | const objects: S3ObjectEntry[] = [] |
| 410 | |
| 411 | for (const match of xml.matchAll(/<Contents>([\s\S]*?)<\/Contents>/g)) { |
| 412 | const block = match[1] |
| 413 | const rawKey = extractTag(block, 'Key') |
| 414 | if (!rawKey) continue |
| 415 | const key = decodeObjectKey(rawKey) |
| 416 | |
| 417 | const etag = normalizeEtag(extractTag(block, 'ETag') ?? '') |
| 418 | const lastModified = extractTag(block, 'LastModified') ?? '' |
| 419 | const size = Number(extractTag(block, 'Size') ?? '0') |
| 420 | |
| 421 | objects.push({ key, etag, lastModified, size: Number.isNaN(size) ? 0 : size }) |
| 422 | } |
| 423 | |
| 424 | const isTruncated = extractTag(xml, 'IsTruncated') === 'true' |
| 425 | const nextContinuationToken = extractTag(xml, 'NextContinuationToken') |
| 426 | |
| 427 | return { objects, isTruncated, nextContinuationToken } |
| 428 | } |
| 429 | |
| 430 | /** |
| 431 | * Builds a metadata stub for an S3 object. The content hash combines the key |
no test coverage detected