Putting a blob in azure from windows store app in javascript

Published March 21, 2013 6:18 pm


I need to put a blob into azure from my windows store app that is being coded in javascript. I did not find azure javascript client lib. Azure does have nodejs sdk but did not find azure javascript client lib. Hence, I coded up my first put blob rest api call. The most tricky part in the code is to generate the authorization shared key. Rest is simply adding properties into XHR call options parameters.

Refer the code below.

var url = 'http://mystorageaccount.blob.core.windows.net/mycontainer/myblob';
                var date = new Date().toGMTString().replace('UTC', 'GMT');
                var xhrOptions = {
                    type: 'PUT', url: url,
                    headers: {
                        'Content-Type': 'application/octet-stream',
                        'Content-Length': contentLength,
                        'x-ms-date': date,
                        'x-ms-version': '2009-09-19',
                        'x-ms-blob-type': 'BlockBlob',
                        data: data
                    }
                };
                var options = { storageAccount: 'mystorageaccount', resourcePath: 'mycontainer/myblob', primaryKey: 'myprimarykey' };
                var authorizationHeaderValue = authorizationHeader.compute(options, xhrOptions);
                xhrOptions.headers.Authorization = authorizationHeaderValue;
                WinJS.xhr(xhrOptions).done(function oncomplete(req)
                {

                }, function onerror(error)
                {
                });
var authorizationHeader = 
{
        /*
        options - refer call to this method
        xhrOptions - parameter for the WinJS.xhr call. computed Authorization header will be used this xhr request only.
        */
        compute: function compute(options, xhrOptions)
        {
            var sig = this._computeSignature(options, xhrOptions);
            var result = 'SharedKey ' + options.storageAccount + ':' + sig;
            return result;
        },
        _computeSignature: function computeSignature(options, xhrOptions)
        {
            var sigString = this._getSignatureString(options, xhrOptions);
            var key = CryptoJS.enc.Base64.parse(options.primaryKey);
            var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);
            hmac.update(sigString);
            var hash = hmac.finalize();
            var result = hash.toString(CryptoJS.enc.Base64);
            return result;
        },
        _getHeaderOrDefault: function getHeaderOrDefault(headers, headerName)
        {
            var result = headers[headerName];
            result = result ? result : '';
            return result;
        },
        _getSignatureString: function createSignatureString(options, xhrOptions)
        {
            var headers = xhrOptions.headers;
            var httpVerb = xhrOptions.type.toUpperCase();
            var sigItems = [];
            sigItems.push(httpVerb);
            var contentEncoding = this._getHeaderOrDefault(headers, 'Content-Encoding');
            sigItems.push(contentEncoding);
            var contentLanguage = this._getHeaderOrDefault(headers, 'Content-Language');
            sigItems.push(contentLanguage);
            var contentLength = this._getHeaderOrDefault(headers, 'Content-Length');
            sigItems.push(contentLength);
            var contentMD5 = this._getHeaderOrDefault(headers, 'Content-MD5');
            sigItems.push(contentMD5);
            var contentType = this._getHeaderOrDefault(headers, 'Content-Type');
            sigItems.push(contentType);
            var date = this._getHeaderOrDefault(headers, 'Date');
            sigItems.push(date);
            var ifModifiedSince = this._getHeaderOrDefault(headers, 'If-Modified-Since');
            sigItems.push(ifModifiedSince);
            var ifMatch = this._getHeaderOrDefault(headers, 'If-Match');
            sigItems.push(ifMatch);
            var ifNoneMatch = this._getHeaderOrDefault(headers, 'If-None-Match');
            sigItems.push(ifNoneMatch);
            var ifUnmodifiedSince = this._getHeaderOrDefault(headers, 'If-Unmodified-Since');
            sigItems.push(ifUnmodifiedSince);
            var range = this._getHeaderOrDefault(headers, 'Range');
            sigItems.push(range);
            var canonicalizedHeadersString = this._getCanonicalizedHeadersString(xhrOptions);
            sigItems.push(canonicalizedHeadersString);
            var canonicalizedResource = this._getCanonicalizedResource(options);
            sigItems.push(canonicalizedResource);

            var result = sigItems.join('\n');
            return result;
        },
        _getCanonicalizedHeadersString: function getCanonicalizedHeadersString(xhrOptions)
        {
            var headers = xhrOptions.headers;
            var headerNames = Object.keys(headers);
            var lowercaseHeaderNames = headerNames.map(function tolower(name)
            {
                return name.toLowerCase();
            });
            var msHeaderNames = lowercaseHeaderNames.filter(function isMsHeader(name)
            {
                if (name.indexOf('x-ms-') == 0)
                    return true;
                else
                    return false;
            });
            msHeaderNames.sort();
            var strItems = [];
            for (var i = 0; i < msHeaderNames.length; i++)
            {
                var key = msHeaderNames[i];
                var value = headers[key] || '';
                value = value.trim();
                var item = key + ':' + value;
                strItems.push(item);
            }

            var result = strItems.join('\n');
            return result;
        },
        _getCanonicalizedResource: function getCanonicalizedResource(options)
        {
            var items = [];
            var path = "/" + options.storageAccount;
            path += "/" + decodeURIComponent(options.resourcePath);
            items.push(path);
            // TODO: handle storage rest api query parameters. 
            // need to add them to options and items 
            // as described here - http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx#Constructing_Element
            var result = items.join('\n');
            return result;
        }
    }

Notes

  1. Code uses the google code crypto lib –  - need to download the zip, extract and use rollups\hmac-sha256.js and components\enc-base64-min.js from the zip file.
  2. CryptoJS.algo.HMAC.create expects the key to be in utf8 or wordskey. Hence, the azure base64key needs to be decoded prior to passing to the call. Thanks to iug to give lead in this.
  3. hmac.update(message) call encodes the message string using utf8. hence, we are good there.

Leave a comment