-
Notifications
You must be signed in to change notification settings - Fork 227
fix(zip): complete ZIP64 header validation #3546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // loaders.gl | ||
| // SPDX-License-Identifier: MIT | ||
| // Copyright (c) vis.gl contributors | ||
|
|
||
| /** ZIP64 extra-field header identifier. */ | ||
| const ZIP64_EXTRA_FIELD_ID = 0x0001; | ||
|
|
||
| /** Sentinel indicating that a 16-bit ZIP header value is stored in ZIP64 data. */ | ||
| export const ZIP64_UINT16_SENTINEL = 0xffffn; | ||
|
|
||
| /** Sentinel indicating that a 32-bit ZIP header value is stored in ZIP64 data. */ | ||
| export const ZIP64_UINT32_SENTINEL = 0xffffffffn; | ||
|
|
||
| /** Description of a value stored in a ZIP64 extended information extra field. */ | ||
| export type Zip64ExtraFieldDescription<FieldName extends string> = { | ||
| /** Name used for the decoded value. */ | ||
| name: FieldName; | ||
| /** Encoded value width in bytes. */ | ||
| byteLength: 4 | 8; | ||
| }; | ||
|
|
||
| /** | ||
| * Finds and decodes the ZIP64 record in a sequence of ZIP extra-field records. | ||
| * @param extraField complete extra-field data from a local or central-directory header | ||
| * @param expectedFields ZIP64 values required by sentinel fields in the legacy header | ||
| * @returns decoded ZIP64 values keyed by the supplied field names | ||
| * @throws If required ZIP64 data is missing, truncated, or has an unexpected size | ||
| */ | ||
| export function parseZip64ExtraField<FieldName extends string>( | ||
| extraField: DataView, | ||
| expectedFields: readonly Zip64ExtraFieldDescription<FieldName>[] | ||
| ): Partial<Record<FieldName, bigint>> { | ||
| const values: Partial<Record<FieldName, bigint>> = {}; | ||
| if (expectedFields.length === 0) { | ||
| return values; | ||
| } | ||
|
|
||
| const expectedPayloadLength = expectedFields.reduce( | ||
| (totalByteLength, field) => totalByteLength + field.byteLength, | ||
| 0 | ||
| ); | ||
| let recordOffset = 0; | ||
|
|
||
| while (recordOffset < extraField.byteLength) { | ||
| if (recordOffset + 4 > extraField.byteLength) { | ||
| throw new Error( | ||
| 'Invalid ZIP archive: truncated extra-field record header while reading ZIP64 data' | ||
| ); | ||
| } | ||
|
|
||
| const headerId = extraField.getUint16(recordOffset, true); | ||
| const payloadLength = extraField.getUint16(recordOffset + 2, true); | ||
| const payloadOffset = recordOffset + 4; | ||
| const nextRecordOffset = payloadOffset + payloadLength; | ||
|
|
||
| if (nextRecordOffset > extraField.byteLength) { | ||
| throw new Error( | ||
| 'Invalid ZIP archive: truncated extra-field record payload while reading ZIP64 data' | ||
| ); | ||
| } | ||
|
|
||
| if (headerId === ZIP64_EXTRA_FIELD_ID) { | ||
| if (payloadLength !== expectedPayloadLength) { | ||
| throw new Error( | ||
| 'Invalid ZIP archive: ZIP64 extended information has an unexpected payload size' | ||
| ); | ||
| } | ||
|
|
||
| let fieldOffset = payloadOffset; | ||
| for (const field of expectedFields) { | ||
| values[field.name] = | ||
| field.byteLength === 8 | ||
| ? extraField.getBigUint64(fieldOffset, true) | ||
| : BigInt(extraField.getUint32(fieldOffset, true)); | ||
| fieldOffset += field.byteLength; | ||
| } | ||
| return values; | ||
| } | ||
|
|
||
| recordOffset = nextRecordOffset; | ||
| } | ||
|
|
||
| throw new Error('Invalid ZIP archive: required ZIP64 extended information is missing'); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a local header has only the uncompressed legacy size set to
0xffffffff(for example, a highly compressible file whose original size exceeds 4 GiB but whose compressed size still fits in 32 bits), this builds an expected ZIP64 payload containing onlyuncompressedSize. ZIP64 local-header entries must include both original and compressed size values (PKWARE APPNOTE 4.5.3), so those archives carry a 16-byte ZIP64 payload and now failfetch()withunexpected payload sizeeven though the central directory can provide the compressed length. For local headers, request both size fields whenever either legacy size uses ZIP64.Useful? React with 👍 / 👎.