Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions okio-testing-support/src/commonMain/resources/go/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The files in this directory are copied from Go:
https://go.dev/

These files are subject to the 3-Clause BSD License:
https://github.com/golang/go/blob/master/LICENSE
Binary file not shown.
Binary file not shown.
69 changes: 20 additions & 49 deletions okio/src/zlibMain/kotlin/okio/ZipFileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,6 @@ import okio.internal.skipLocalHeader
/**
* Read only access to a [zip file][zip_format] and common [extra fields][extra_fields].
*
* Zip Timestamps
* --------------
*
* The base zip format tracks the [last modified timestamp][FileMetadata.lastModifiedAtMillis]. It
* does not track [created timestamps][FileMetadata.createdAtMillis] or [last accessed
* timestamps][FileMetadata.lastAccessedAtMillis]. This format has limitations:
*
* * Timestamps are 16-bit values stored with 2-second precision. Some zip encoders (WinZip, PKZIP)
* round up to the nearest 2 seconds; other encoders (Java) round down.
*
* * Timestamps before 1980-01-01 cannot be represented. They cannot represent dates after
* 2107-12-31.
*
* * Timestamps are stored in local time with no time zone offset. If the time zone offset changes
* – due to daylight savings time or the zip file being sent to another time zone – file times
* will be incorrect. The file time will be shifted by the difference in time zone offsets
* between the encoder and decoder.
*
* The zip format has optional extensions for timestamps.
*
* * UNIX timestamps (0x000d) support both last-access time and last modification time. These
* timestamps are stored with 1-second precision using UTC.
*
* * NTFS timestamps (0x000a) support creation time, last access time, and last modified time.
* These timestamps are stored with 100-millisecond precision using UTC.
*
* * Extended timestamps (0x5455) are stored as signed 32-bit timestamps with 1-second precision.
* These cannot express dates beyond 2038-01-19.
*
* This class currently supports base timestamps and extended timestamps.
*
* [zip_format]: https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE_6.2.0.txt
* [extra_fields]: https://opensource.apple.com/source/zip/zip-6/unzip/unzip/proginfo/extra.fld
*/
Expand All @@ -81,27 +50,29 @@ internal class ZipFileSystem internal constructor(

override fun metadataOrNull(path: Path): FileMetadata? {
val canonicalPath = canonicalizeInternal(path)
val entry = entries[canonicalPath] ?: return null

val basicMetadata = FileMetadata(
isRegularFile = !entry.isDirectory,
isDirectory = entry.isDirectory,
symlinkTarget = null,
size = if (entry.isDirectory) null else entry.size,
createdAtMillis = null,
lastModifiedAtMillis = entry.lastModifiedAtMillis,
lastAccessedAtMillis = null,
)
val centralDirectoryEntry = entries[canonicalPath] ?: return null

val fullEntry = when {
centralDirectoryEntry.offset != -1L -> {
fileSystem.openReadOnly(zipPath).use { fileHandle ->
return@use fileHandle.source(centralDirectoryEntry.offset).buffer().use { source ->
source.readLocalHeader(centralDirectoryEntry)
}
}
}

if (entry.offset == -1L) {
return basicMetadata
else -> centralDirectoryEntry
}

return fileSystem.openReadOnly(zipPath).use { fileHandle ->
return@use fileHandle.source(entry.offset).buffer().use { source ->
source.readLocalHeader(basicMetadata)
}
}
return FileMetadata(
isRegularFile = !fullEntry.isDirectory,
isDirectory = fullEntry.isDirectory,
symlinkTarget = null,
size = if (fullEntry.isDirectory) null else fullEntry.size,
createdAtMillis = fullEntry.createdAtMillis,
lastModifiedAtMillis = fullEntry.lastModifiedAtMillis,
lastAccessedAtMillis = fullEntry.lastAccessedAtMillis,
)
}

override fun openReadOnly(file: Path): FileHandle {
Expand Down
90 changes: 88 additions & 2 deletions okio/src/zlibMain/kotlin/okio/internal/ZipEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
*/
package okio.internal

import okio.FileMetadata
import okio.Path

/**
* This class prefers NTFS timestamps, then extended timestamps, then the base ZIP timestamps.
*/
internal class ZipEntry(
/**
* Absolute path of this entry. If the raw name on disk contains relative paths like `..`, they
Expand All @@ -43,9 +47,91 @@ internal class ZipEntry(
/** Either [COMPRESSION_METHOD_DEFLATED] or [COMPRESSION_METHOD_STORED]. */
val compressionMethod: Int = -1,

val lastModifiedAtMillis: Long? = null,

val offset: Long = -1L,

/**
* The base ZIP format tracks the [last modified timestamp][FileMetadata.lastModifiedAtMillis]. It
* does not track [created timestamps][FileMetadata.createdAtMillis] or [last accessed
* timestamps][FileMetadata.lastAccessedAtMillis].
*
* This format has severe limitations:
*
* * Timestamps are 16-bit values stored with 2-second precision. Some zip encoders (WinZip,
* PKZIP) round up to the nearest 2 seconds; other encoders (Java) round down.
*
* * Timestamps before 1980-01-01 cannot be represented. They cannot represent dates after
* 2107-12-31.
*
* * Timestamps are stored in local time with no time zone offset. If the time zone offset
* changes – due to daylight savings time or the zip file being sent to another time zone –
* file times will be incorrect. The file time will be shifted by the difference in time zone
* offsets between the encoder and decoder.
*/
val dosLastModifiedAtDate: Int = -1,
val dosLastModifiedAtTime: Int = -1,

/**
* NTFS timestamps (0x000a) support creation time, last access time, and last modified time.
* These timestamps are stored with 100-millisecond precision using UTC.
*/
val ntfsLastModifiedAtFiletime: Long? = null,
val ntfsLastAccessedAtFiletime: Long? = null,
val ntfsCreatedAtFiletime: Long? = null,

/**
* Extended timestamps (0x5455) are stored as signed 32-bit timestamps with 1-second precision.
* These cannot express dates beyond 2038-01-19.
*/
val extendedLastModifiedAtSeconds: Int? = null,
val extendedLastAccessedAtSeconds: Int? = null,
val extendedCreatedAtSeconds: Int? = null,
) {
val children = mutableListOf<Path>()

internal fun copy(
extendedLastModifiedAtSeconds: Int?,
extendedLastAccessedAtSeconds: Int?,
extendedCreatedAtSeconds: Int?,
) = ZipEntry(
canonicalPath = canonicalPath,
isDirectory = isDirectory,
comment = comment,
crc = crc,
compressedSize = compressedSize,
size = size,
compressionMethod = compressionMethod,
offset = offset,
dosLastModifiedAtDate = dosLastModifiedAtDate,
dosLastModifiedAtTime = dosLastModifiedAtTime,
ntfsLastModifiedAtFiletime = ntfsLastModifiedAtFiletime,
ntfsLastAccessedAtFiletime = ntfsLastAccessedAtFiletime,
ntfsCreatedAtFiletime = ntfsCreatedAtFiletime,
extendedLastModifiedAtSeconds = extendedLastModifiedAtSeconds,
extendedLastAccessedAtSeconds = extendedLastAccessedAtSeconds,
extendedCreatedAtSeconds = extendedCreatedAtSeconds,
)

internal val lastAccessedAtMillis: Long?
get() = when {
ntfsLastAccessedAtFiletime != null -> filetimeToEpochMillis(ntfsLastAccessedAtFiletime)
extendedLastAccessedAtSeconds != null -> extendedLastAccessedAtSeconds * 1000L
else -> null
}

internal val lastModifiedAtMillis: Long?
get() = when {
ntfsLastModifiedAtFiletime != null -> filetimeToEpochMillis(ntfsLastModifiedAtFiletime)
extendedLastModifiedAtSeconds != null -> extendedLastModifiedAtSeconds * 1000L
dosLastModifiedAtTime != -1 -> {
dosDateTimeToEpochMillis(dosLastModifiedAtDate, dosLastModifiedAtTime)
}
else -> null
}

internal val createdAtMillis: Long?
get() = when {
ntfsCreatedAtFiletime != null -> filetimeToEpochMillis(ntfsCreatedAtFiletime)
extendedCreatedAtSeconds != null -> extendedCreatedAtSeconds * 1000L
else -> null
}
}
Loading