0
votes

I want to download file from azure with REST API and I written below code for iOS Swift >3, but when I run the download task, get this error:

InvalidHeaderValueThe value for one of the HTTP headers is not in the correct format. RequestId:10d9c8f8-001a-00db-283c-1ab1d1000000 Time:2017-08-21T05:14:18.2768791Zx-ms-version

private let account = "myAccount"
private let key = "My key is encrypted as base64"
private let fileName = "My file name which have to download"
private let SHARE_NAME = "My share name"
let date  = Date().currentTimeZoneDate() + " GMT"

func downloadFileFromAzure(fileName:String)
{         
    // Create destination URL 
    let documentsUrl:URL =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
    let destinationFileUrl = documentsUrl.appendingPathComponent(fileName)
    //Create URL to the source file you want to download

    let fileURL = URL(string: "https://\(account).file.core.windows.net/\(SHARE_NAME)/\(fileName)")!
    //create session
    let sessionConfig = URLSessionConfiguration.default
    let session = URLSession(configuration: sessionConfig)
   //create headers field
    var request = URLRequest(url:fileURL)

    request.setValue(date,forHTTPHeaderField: "x-ms-date")
    request.setValue("2014-02-14", forHTTPHeaderField: "x-ms-version")
    request.setValue("\(getFileRequest(account:account, fileName: fileName))", forHTTPHeaderField: "Authorization")

    //download files

     let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in

        if let tempLocalUrl = tempLocalUrl, error == nil {
            // Success
            if let statusCode = (response as? HTTPURLResponse)?.statusCode {
                print("Successfully downloaded. Status code: \(statusCode)")
            }

            do {
                try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
                print("tempLocalUrl: \(tempLocalUrl)")
                print("destinationFileUrl: \(destinationFileUrl)")
                //reading
                do {
                    let text = try String(contentsOf: destinationFileUrl, encoding: String.Encoding.utf8)
                    print("reading files data : \(text)")
                }
                catch (let writeError){ 

                    print("Error reading a file \(writeError)")

                }

            } catch (let writeError) {
                print("Error creating a file \(destinationFileUrl) : \(writeError)")
            }

        } else {
            print("Error took place while downloading a file. Error description: %@", error?.localizedDescription);
        }
    }
    task.resume()
 }


   public func getFileRequest(account:String,fileName:String)->String
   {        

    let canonicalizedResources = "/\(account)/\(SHARE_NAME)/\(fileName)"

    let  canonicalizedHeaders = "x-ms-date:\(date)\nx-ms-version:2014-02-14"

    let stringToSign = "GET\n\n\n\n\n\n\n\n\n\n\n\n\(canonicalizedHeaders)\n\(canonicalizedResources)"

    let auth = getAuthenticationString(stringToSign:stringToSign);
    return auth

}

///getAuthenticationString
public func getAuthenticationString(stringToSign:String)->String
{                              
    let authKey: String = stringToSign.hmac(algorithm: HMACAlgorithm.SHA256, key:  key)
    let auth = "SharedKey " + account + ":" + authKey;

    return auth;
}

enum HMACAlgorithm {

case MD5, SHA1, SHA224, SHA256, SHA384, SHA512;

func toCCHmacAlgorithm() -> CCHmacAlgorithm {
    var result: Int = 0
    switch self {
    case .MD5:
        result = kCCHmacAlgMD5
    case .SHA1:
        result = kCCHmacAlgSHA1
    case .SHA224:
        result = kCCHmacAlgSHA224
    case .SHA256:
        result = kCCHmacAlgSHA256
    case .SHA384:
        result = kCCHmacAlgSHA384
    case .SHA512:
        result = kCCHmacAlgSHA512
    }
    return CCHmacAlgorithm(result)
 }

func digestLength() -> Int {
    var result: CInt = 0
    switch self {
    case .MD5:
        result = CC_MD5_DIGEST_LENGTH
    case .SHA1:
        result = CC_SHA1_DIGEST_LENGTH
    case .SHA224:
        result = CC_SHA224_DIGEST_LENGTH
    case .SHA256:
        result = CC_SHA256_DIGEST_LENGTH
    case .SHA384:
        result = CC_SHA384_DIGEST_LENGTH
    case .SHA512:
        result = CC_SHA512_DIGEST_LENGTH
    }
    return Int(result)
}

}

extension String {
func hmac(algorithm: HMACAlgorithm, key: String) -> String {

    let keyBytes = key.base64DecodeAsData() 
    let dataBytes = self.cString(using: String.Encoding.utf8)
    var result = [CUnsignedChar](repeating: 0, count: Int(algorithm.digestLength()))
    CCHmac(algorithm.toCCHmacAlgorithm(), keyBytes.bytes, keyBytes.length, dataBytes!, Int(strlen(dataBytes!)), &result)
    let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))
    let hmacBase64 = hmacData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)
    return String(hmacBase64)
} 

func base64DecodeAsData() -> NSData {
    let decodedData = NSData(base64Encoded: self, options: NSData.Base64DecodingOptions(rawValue: 0))
    return decodedData!

}

Edit:

I only get this error in the Xcode.

InvalidHeaderValueThe value for one of the HTTP headers is not in the correct format. RequestId:10d9c8f8-001a-00db-283c-1ab1d1000000 Time:2017-08-21T05:14:18.2768791Zx-ms-version

But when I tried sharedKey(which is generated in ios) in the android source get this error:

AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.equestId:379c5c1b-001a-0017-1a19-1bd564000000ime:2017-08-22T07:38:04.8712051ZThe MAC signature found in the HTTP request 'tJcl9LyzF2BzlZMdW9ULtMojDamn9HnEY9LulpDOsYg=' is not the same as any computed signature. Server used following string to sign: 'GETx-ms-date:Tue, 22 Aug 2017 07:32:52 GMT-ms-version:2014-02-14account/SHARE_NAME/fileName'.

2
x-ms-version:2014-02-14 also return same error. - idris yıldız
Normally you should see more details in your error like which header's value is incorrect. You may need to read the response stream to get these details. - Gaurav Mantri
How can I read response steam for above source. The response has no memeber 'stream'? @GauravMantri - idris yıldız
Unfortunately I haven't worked with Swift so I can't tell you how you could do that. In your error variable do you get more details or only the one you posted? - Gaurav Mantri
Only get this error in the Xcode, but when I tried this authString in android, get this error: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.equestId:379c5c1b-001a-0017-1a19-1bd564000000ime:2017-08-22T07:38:04.8712051Z....The MAC signature found in the HTTP request '.....' is not the same as any computed signature. Server used following string to sign: 'GETx-ms-date:Tue, 22 Aug 2017 07:32:52 GMT-ms-version:2014-02-14....'. - idris yıldız

2 Answers

1
votes

SOLVED

Change this line

let hmacBase64 = hmacData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)

To:

let hmacBase64 = hmacData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))

Also change this line

let stringToSign = "GET\n\n\n\n\n\n\n\n\n\n\n\n(canonicalizedHeaders)\n(canonicalizedResources)"

To:

let stringToSign = "GET\n" + "\n" // content encoding + "\n" // content language + "\n" // content length + "\n" // content md5 + "\n" // content type + "\n" // date + "\n" // if modified since + "\n" // if match + "\n" // if none match + "\n" // if unmodified since + "\n" // range + "(canonicalizedHeaders)\n" //headers + "(canonicalizedResources)"// resources

Please delete your stored file before create new one by this func:

func deleteFileFromDirectory(fileNameToDelete:String)
{     

    var filePath = ""

    // Fine documents directory on device
    let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)

    if dirs.count > 0 {
        let dir = dirs[0] //documents directory
        filePath = dir.appendingFormat("/" + fileNameToDelete)
        print("Local path = \(filePath)")

    } else {
        print("Could not find local directory to store file")
        return
    }


    do {
        let fileManager = FileManager.default

        // Check if file exists
        if fileManager.fileExists(atPath: filePath) {
            // Delete file
            try fileManager.removeItem(atPath: filePath)
        } else {
            print("File does not exist")
        }

    }
    catch let error as NSError {
        print("An error took place: \(error)")
    }

}
0
votes

I believe the problem is in these 2 lines of code:

let  canonicalizedHeaders = "x-ms-date:\(date)\nx-ms-version:2016-05-31\n"

let stringToSign = "GET\n\n\n\n\n\n\n\n\n\n\n\n\(canonicalizedHeaders)\n\(canonicalizedResources)"

I noticed there's an extra new line character (\n). Can you either remove the \n character at the end of canonicalizedHeaders or between (canonicalizedHeaders) and (canonicalizedResources) in stringToSign?