3

This question is regarding a DMARC report viewer app in iOS 13 using SwiftUI and Gmail API. The reports are mailed to our admin email id by google in xml format which will be zipped. So basically it is a zip attachment. So here, GMail API is used to access those specific mail using filter and got all the base64 encoded data from API. Also decoded it to Data type data. That far is OK. Next part is where data of zip file in byte format is decompressed and extract xml file inside in String type. Then I need to parse XML. I think I can figure out parsing with XMLParser.

Question: how to decompress zip file in Data type and get xml file from it as String type?

INPUT: String in Base64 format from GMail API fetch (A zip file attachment with only 1 xml file inside)
OUTPUT: String in XML format
PLATFORM: iOS 13/Swift 5.2/SwiftUI/Xcode 11.4
ACTION: 

(INPUT)
base64: String | Decode -> Data
attachment.zip: Data | Decompress -> [Data]
ListOfFiles: [Data] | FirstIndex -> Data
dmarc.xml: Data | ContentOfXML -> String
(OUTPUT)

Update: I have tried an external package called Zip and it also failed.

let path = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let url = path.appendingPathComponent(messageId+".zip")
do {
    try data.write(to: url)
} catch {
    print("Error while writing: "+error.localizedDescription)
}
do {
    let unzipDirectory = try Zip.quickUnzipFile(url)
    print(unzipDirectory)
} catch let error as NSError {
    print("Error while unzipping: "+error.localizedDescription)
}

This code resulted in following error

Error while unzipping: The operation couldn’t be completed. (Zip.ZipError error 1.)
NikzJon
  • 912
  • 7
  • 25
  • 1) Did you verify that the base 64 encoded stuff from Gmail is valid? Can you decode it manually? 2) Did you verify that the decoded base 64 data are really a ZIP file? Did you try to get this file from iPhone simulator and unzip it on your Mac for example? 3) There're other packages that allows you to extract just on file - [ZIPFoundation](https://github.com/weichsel/ZIPFoundation#accessing-individual-entries) for example. – zrzka May 27 '20 at 08:29
  • I kind of figured out the core problem. https://www.w3.org/Protocols/rfc1341/5_Content-Transfer-Encoding.html this is what google use for the attachment's encoding. This is not standard in most decoder. Hence it looks like broken. https://stackoverflow.com/a/58590759/2382813 – NikzJon Jun 01 '20 at 16:29
  • I think I need to find a way to convert base64 from 7bit format to 8bit format and then decompress. – NikzJon Jun 01 '20 at 16:31

1 Answers1

0

Finally I found it. As it is mentioned in Ref 1,The email bodies are encoded in 7-bit US-ASCII data. So this is why the base64 decoding did not work.

As defined in the rfc1341:

An encoding type of 7BIT requires that the body is already in a seven-bit mail- ready representation. This is the default value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the Content-Transfer-Encoding header field is not present.

The whole code worked after adding the following.

let edata: String = result.data.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")

As it is mentioned in Ref 2, it just need character replacement on '-' with '+' and '_' with '/' inside base64 data received from gmail api.

func getAttachedData(messageId: String, attachmentId: String) {
    decode(self.urlBase+messageId+"/attachments/"+attachmentId+"?"+self.urlKey) { (result: Attachment) in
        let edata: String = result.data.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
        if let data = Data(base64Encoded: edata, options: .ignoreUnknownCharacters) {
            let filemanager = FileManager.default
            let path = try! filemanager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            let url = path.appendingPathComponent(messageId+".zip")
            do {
                try data.write(to: url)
            } catch {
                print("Error while writing: "+error.localizedDescription)
            }
            do {
                let unzipDirectory = try Zip.quickUnzipFile(url)
                print("Unzipped")
                do {
                    let filelist = try filemanager.contentsOfDirectory(at: unzipDirectory, includingPropertiesForKeys: [], options: [])

                    for filename in filelist {
                        print(filename.lastPathComponent)
                        print(filename.relativeString)
                        do {
                            let text = try String(contentsOf: filename, encoding: .utf8)
                            print(text)
                            DispatchQueue.main.async {
                                self.attachments.append(text)
                            }
                        } catch let error as NSError {
                            print("Error: \(error.localizedDescription)")
                        }
                    }
                } catch let error {
                    print("Error: \(error.localizedDescription)")
                }
            } catch let error as NSError {
                print("Error while unzipping: "+error.localizedDescription)
            }
        }
    }
}

Ref 1: https://stackoverflow.com/a/58590759/2382813

Ref 2: https://stackoverflow.com/a/24986452/2382813

NikzJon
  • 912
  • 7
  • 25