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'? @GauravMantriidris 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?