Skip to content
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

Finally - A Proposed Working Solution to Efficiently Unzipping Password-Protected File in Memory Using this Swift Library (Solution is Here) #269

Open
waelsaad opened this issue Sep 20, 2024 · 0 comments

Comments

@waelsaad
Copy link

waelsaad commented Sep 20, 2024

Hi,

I asked this question last Feb but didn't get any response. #257

I would like now to share my solution to unzipInMemory and it seems to work just fine, please see if you like to use it or make it better and hopefully integrate it in this nice library.

The only method you need is unzipInMemory to be added to zip.swift file. I have also included a nice URL extension to use along with sample usage.

If you like it feel free to nominate a star :] https://stars.github.com/nominate/

Also please let me know if there are issues with the solution or if you have a better approach. Also if we need to create another method for unzipping multiple files in memory or modify this one.

/**
   Unzip data in memory.

   - parameter data: The Data object containing the zip file content.
   - parameter password: An optional password string for encrypted zip files.
   - parameter fileOutputHandler: A closure called for each unzipped file, providing the unzipped data and the file name.
   - parameter progress: A progress closure called after unzipping each file in the archive, with a Double value between 0 and 1.

   - throws: Error if unzipping fails.

   - notes: Supports implicit progress composition.
   */
  
  class func unzipInMemory(_ zipFilePath: URL, password: String?, fileOutputHandler: @escaping (_ unzippedData: Data, _ fileName: String) -> Void) throws {
      
      // File manager
      let fileManager = FileManager.default
      
      // Check whether a zip file exists at path.
      let path = zipFilePath.path
      
      guard fileManager.fileExists(atPath: path), !fileExtensionIsInvalid(zipFilePath.pathExtension) else {
          print("File not found at path: \(path)")
          throw ZipError.fileNotFound
      }
      
      // Unzip setup
      var ret: Int32 = 0
      let bufferSize: UInt32 = 4096
      var buffer = [CUnsignedChar](repeating: 0, count: Int(bufferSize))
      
      // Begin unzipping
      let zip = unzOpen64(path)
      defer { unzClose(zip) }
      
      if unzGoToFirstFile(zip) != UNZ_OK {
          throw ZipError.unzipFail
      }
      
      repeat {
          var readBytes: Int32 = 0
          
          // Open the current file
          if let cPassword = password?.cString(using: .ascii) {
              ret = unzOpenCurrentFilePassword(zip, cPassword)
          } else {
              ret = unzOpenCurrentFile(zip)
          }
          
          guard ret == UNZ_OK else {
              throw ZipError.unzipFail
          }
          
          var fileInfo = unz_file_info64()
          memset(&fileInfo, 0, MemoryLayout<unz_file_info>.size)
          
          ret = unzGetCurrentFileInfo64(zip, &fileInfo, nil, 0, nil, 0, nil, 0)
          guard ret == UNZ_OK else {
              unzCloseCurrentFile(zip)
              throw ZipError.unzipFail
          }
          
          let fileNameSize = Int(fileInfo.size_filename) + 1
          let fileName = UnsafeMutablePointer<CChar>.allocate(capacity: fileNameSize)
          defer { free(fileName) }
          
          unzGetCurrentFileInfo64(zip, &fileInfo, fileName, UInt(fileNameSize), nil, 0, nil, 0)
          fileName[Int(fileInfo.size_filename)] = 0
          
          let pathString = String(cString: fileName)
          var fileData = Data()

          // Read file data
          repeat {
              readBytes = unzReadCurrentFile(zip, &buffer, bufferSize)
              if readBytes > 0 {
                  fileData.append(buffer, count: Int(readBytes))
              }
          } while readBytes > 0
          
          // Close the current file
          let crcRet = unzCloseCurrentFile(zip)
          guard crcRet == UNZ_OK else {
              throw ZipError.unzipFail
          }
          
          // Call the output handler with the unzipped data
          fileOutputHandler(fileData, pathString)
          
          // Move to the next file
          ret = unzGoToNextFile(zip)
          
      } while (ret == UNZ_OK)
  }
extension URL {
  
  /**
   Unzip a zip file located at the URL.

   - parameter hash: An optional password string for encrypted zip files.
   - parameter progress: An optional closure called with the progress of unzipping, where the Double value is between 0 and 1.

   - returns: The content of the unzipped file as a String, or nil if unzipping fails.

   - throws: An error if unzipping fails.

   - notes: Supports sequential processing of unzipped files.
   */
  
  func unzip(hash: String? = nil, progress: ((Double) -> Void)? = nil) -> String? {
      var extractedContent: String?
      let semaphore = DispatchSemaphore(value: 0)
      
      do {
          try Zip.unzipInMemory(self, password: hash) { unzippedData, fileName in
              print("Unzipped file: \(fileName), data size: \(unzippedData.count) bytes")
              extractedContent = String(decoding: unzippedData, as: UTF8.self)
              
              if let content = extractedContent {
                  print("Content of \(fileName):\n\(content)")
              } else {
                  print("Unable to convert unzipped data to string for file: \(fileName)")
              }
              
              semaphore.signal()
          }
          
          semaphore.wait()
          return extractedContent
          
      } catch {
          print("Error unzipping data: \(error.localizedDescription)")
          return nil
      }
  }
  
}

// MARK: Sample Code - Usage:

struct Constants {
  static let hash = "your_password_hash" // Replace with your actual hash
}

// URL of the zip file
let fileURL = URL(fileURLWithPath: "path/to/your/file.zip")

do {
  try fileURL.unzip(hash: Constants.hash) { unzippedFile in
      print("Unzipped file: \(unzippedFile.lastPathComponent)")
  } progress: { progress in
      print("Progress: \(progress * 100)%")
  }
} catch {
  print("An error occurred during unzipping: \(error.localizedDescription)")
}
@waelsaad waelsaad changed the title Finally - A Proposed Working Solution to Efficiently Unzipping Password-Protected File in Memory Using Swift (Solution is Here) Finally - A Proposed Working Solution to Efficiently Unzipping Password-Protected File in Memory Using this Swift Library (Solution is Here) Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant