@@ -242,6 +242,37 @@ struct ArchiveTests {
242242 #expect( try String ( contentsOf: extractDir. appendingPathComponent ( " subdir/file2.txt " ) , encoding: . utf8) == " world " )
243243 }
244244
245+ @Test func archiveDirectoryPreservesReadonlySubdirectory( ) throws {
246+ let testDir = createTemporaryDirectory ( baseName: " ArchiveTests.archiveDirReadonlySubdir " ) !
247+ defer { try ? FileManager . default. removeItem ( at: testDir) }
248+
249+ let sourceDir = testDir. appendingPathComponent ( " source " )
250+ let readonlyDir = sourceDir. appendingPathComponent ( " readonly " )
251+ try FileManager . default. createDirectory ( at: readonlyDir, withIntermediateDirectories: true )
252+ try " content " . write ( to: readonlyDir. appendingPathComponent ( " file.txt " ) , atomically: true , encoding: . utf8)
253+ try FileManager . default. setAttributes ( [ . posixPermissions: 0o555 ] , ofItemAtPath: readonlyDir. path)
254+ defer {
255+ try ? FileManager . default. setAttributes ( [ . posixPermissions: 0o755 ] , ofItemAtPath: readonlyDir. path)
256+ }
257+
258+ let archiveURL = testDir. appendingPathComponent ( " test.tar.gz " )
259+ let writer = try ArchiveWriter ( format: . pax, filter: . gzip, file: archiveURL)
260+ try writer. archiveDirectory ( sourceDir)
261+ try writer. finishEncoding ( )
262+
263+ let extractDir = testDir. appendingPathComponent ( " extract " )
264+ let reader = try ArchiveReader ( file: archiveURL)
265+ let rejected = try reader. extractContents ( to: extractDir)
266+
267+ #expect( rejected. isEmpty)
268+ #expect(
269+ try String ( contentsOf: extractDir. appendingPathComponent ( " readonly/file.txt " ) , encoding: . utf8)
270+ == " content " )
271+ let attrs = try FileManager . default. attributesOfItem ( atPath: extractDir. appendingPathComponent ( " readonly " ) . path)
272+ let perms = ( attrs [ . posixPermissions] as? NSNumber ) ? . uint16Value ?? 0
273+ #expect( ( perms & 0o777 ) == 0o555 , " Read-only directory permissions should be preserved " )
274+ }
275+
245276 @Test func archiveDirectoryEmpty( ) throws {
246277 let testDir = createTemporaryDirectory ( baseName: " ArchiveTests.archiveDirEmpty " ) !
247278 defer { try ? FileManager . default. removeItem ( at: testDir) }
@@ -722,7 +753,7 @@ struct ArchiveTests {
722753 let readonlyDir = sourceDir. appendingPathComponent ( " readonly " )
723754 try FileManager . default. createDirectory ( at: readonlyDir, withIntermediateDirectories: true )
724755 try " content " . write ( to: readonlyDir. appendingPathComponent ( " file.txt " ) , atomically: true , encoding: . utf8)
725- try FileManager . default. setAttributes ( [ . posixPermissions: 0o777 ] , ofItemAtPath: readonlyDir. path)
756+ try FileManager . default. setAttributes ( [ . posixPermissions: 0o555 ] , ofItemAtPath: readonlyDir. path)
726757 defer {
727758 try ? FileManager . default. setAttributes ( [ . posixPermissions: 0o755 ] , ofItemAtPath: readonlyDir. path)
728759 }
@@ -742,7 +773,7 @@ struct ArchiveTests {
742773 == " content " )
743774 let attrs = try FileManager . default. attributesOfItem ( atPath: extractDir. appendingPathComponent ( " readonly " ) . path)
744775 let perms = ( attrs [ . posixPermissions] as? NSNumber ) ? . uint16Value ?? 0
745- #expect( ( perms & 0o777 ) == 0o777 , " Read-only directory permissions should be preserved " )
776+ #expect( ( perms & 0o777 ) == 0o555 , " Read-only directory permissions should be preserved " )
746777 }
747778
748779 @Test func archiveURLsSymlinks( ) throws {
0 commit comments