@@ -714,6 +714,37 @@ struct ArchiveTests {
714714 #expect( try String ( contentsOf: extractDir. appendingPathComponent ( " subdir/nested/deep.txt " ) , encoding: . utf8) == " deep content " )
715715 }
716716
717+ @Test func archiveURLsDirectoryPreservesPermissions( ) throws {
718+ let testDir = createTemporaryDirectory ( baseName: " ArchiveTests.archiveURLsDirPerms " ) !
719+ defer { try ? FileManager . default. removeItem ( at: testDir) }
720+
721+ let sourceDir = testDir. appendingPathComponent ( " source " )
722+ let readonlyDir = sourceDir. appendingPathComponent ( " readonly " )
723+ try FileManager . default. createDirectory ( at: readonlyDir, withIntermediateDirectories: true )
724+ try " content " . write ( to: readonlyDir. appendingPathComponent ( " file.txt " ) , atomically: true , encoding: . utf8)
725+ try FileManager . default. setAttributes ( [ . posixPermissions: 0o777 ] , ofItemAtPath: readonlyDir. path)
726+ defer {
727+ try ? FileManager . default. setAttributes ( [ . posixPermissions: 0o755 ] , ofItemAtPath: readonlyDir. path)
728+ }
729+
730+ let archiveURL = testDir. appendingPathComponent ( " test.tar.gz " )
731+ let writer = try ArchiveWriter ( format: . pax, filter: . gzip, file: archiveURL)
732+ try writer. archive ( [ FilePath ( readonlyDir. path) ] , base: FilePath ( sourceDir. path) )
733+ try writer. finishEncoding ( )
734+
735+ let extractDir = testDir. appendingPathComponent ( " extract " )
736+ let reader = try ArchiveReader ( file: archiveURL)
737+ let rejected = try reader. extractContents ( to: extractDir)
738+
739+ #expect( rejected. isEmpty)
740+ #expect(
741+ try String ( contentsOf: extractDir. appendingPathComponent ( " readonly/file.txt " ) , encoding: . utf8)
742+ == " content " )
743+ let attrs = try FileManager . default. attributesOfItem ( atPath: extractDir. appendingPathComponent ( " readonly " ) . path)
744+ let perms = ( attrs [ . posixPermissions] as? NSNumber ) ? . uint16Value ?? 0
745+ #expect( ( perms & 0o777 ) == 0o777 , " Read-only directory permissions should be preserved " )
746+ }
747+
717748 @Test func archiveURLsSymlinks( ) throws {
718749 let testDir = createTemporaryDirectory ( baseName: " ArchiveTests.archiveURLsSymlinks " ) !
719750 defer { try ? FileManager . default. removeItem ( at: testDir) }
0 commit comments