// 
// Copyright (c) Microsoft and contributors.  All rights reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 
// See the License for the specific language governing permissions and
// limitations under the License.
// 

// Module dependencies.
var _ = require('underscore');
var qs = require('querystring');

var azureutil = require('../util/util');
var HmacSha256Sign = require('./hmacsha256sign');
var SR = require('../util/sr');
var errors = require('../errors/errors');
var ArgumentError = errors.ArgumentError;

var Constants = require('../util/constants');
var HeaderConstants = Constants.HeaderConstants;
var QueryStringConstants = Constants.QueryStringConstants;
var HeaderConstants = Constants.HeaderConstants;
var CompatibleVersionConstants = Constants.CompatibleVersionConstants;

/**
* Creates a new SharedKey object.
*
* @constructor
* @param {string} storageAccount    The storage account.
* @param {string} storageAccessKey  The storage account's access key.
* @param {bool}   usePathStyleUri   Boolean value indicating if the path, or the hostname, should include the storage account.
*/
function SharedKey(storageAccount, storageAccessKey, usePathStyleUri) {
  this.storageAccount = storageAccount;
  this.storageAccessKey = storageAccessKey;
  this.usePathStyleUri = usePathStyleUri;
  this.signer = new HmacSha256Sign(storageAccessKey);
}

/**
* Generates the shared access signature for a account.
* For more detailed information, refer to https://msdn.microsoft.com/en-us/library/azure/mt584140.aspx
*
* @param {object}                     sharedAccessPolicy                                The shared access policy.
* @param {SharedAccessServices}       sharedAccessPolicy.AccessPolicy.Services          The services (blob, file, queue, table) for a shared access signature associated with this shared access policy.
*                                                                                       Refer to `Constants.AccountSasConstants.Services`.
* @param {SharedAccessResourceTypes}  sharedAccessPolicy.AccessPolicy.ResourceTypes     The resource type for a shared access signature associated with this shared access policy.
*                                                                                       Refer to `Constants.AccountSasConstants.Resources`.
* @param {SharedAccessPermissions}    sharedAccessPolicy.AccessPolicy.Permissions       The permissions for a shared access signature. 
*                                                                                       Refer to `Constants.AccountSasConstants.Permissions`.
* @param {date}                       sharedAccessPolicy.AccessPolicy.Start             The time at which the Shared Access Signature becomes valid.
* @param {date}                       sharedAccessPolicy.AccessPolicy.Expiry            The time at which the Shared Access Signature becomes expired.
* @param {string}                     sharedAccessPolicy.AccessPolicy.IPAddressOrRange  An IP address or a range of IP addresses from which to accept requests. When specifying a range, note that the range is inclusive.
* @param {string}                     sharedAccessPolicy.AccessPolicy.Protocols         The protocols permitted for a request made with the account SAS. 
*                                                                                       Possible values are both HTTPS and HTTP (https,http) or HTTPS only (https). The default value is https,http. 
*                                                                                       Refer to `Constants.AccountSasConstants.Protocols`.
* @return {string} The shared access signature.
*/
SharedKey.prototype.generateAccountSignedQueryString = function (sharedAccessPolicy) {
  var addIfNotNull = function (queryString, name, value) {
    if (!azureutil.objectIsNull(name) && !azureutil.objectIsNull(value)) {
      queryString[name] = value;
    }
  };

  var formatAccessPolicyDates = function (accessPolicy) {
    if (!azureutil.objectIsNull(accessPolicy.Start)) {
      if (!_.isDate(accessPolicy.Start)) {
        accessPolicy.Start = new Date(accessPolicy.Start);
      }
 
      accessPolicy.Start = azureutil.truncatedISO8061Date(accessPolicy.Start);
    }

    if (!azureutil.objectIsNull(accessPolicy.Expiry)) {
      if (!_.isDate(accessPolicy.Expiry)) {
        accessPolicy.Expiry = new Date(accessPolicy.Expiry);
      }

      accessPolicy.Expiry = azureutil.truncatedISO8061Date(accessPolicy.Expiry);
    }
  };

  var queryString = {};

  addIfNotNull(queryString, QueryStringConstants.SIGNED_VERSION, HeaderConstants.TARGET_STORAGE_VERSION);

  // add shared access policy params
  if (sharedAccessPolicy.AccessPolicy) {
    formatAccessPolicyDates(sharedAccessPolicy.AccessPolicy);

    addIfNotNull(queryString, QueryStringConstants.SIGNED_SERVICES, sharedAccessPolicy.AccessPolicy.Services);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_RESOURCE_TYPES, sharedAccessPolicy.AccessPolicy.ResourceTypes);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_PERMISSIONS, sharedAccessPolicy.AccessPolicy.Permissions);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_START, sharedAccessPolicy.AccessPolicy.Start);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_EXPIRY, sharedAccessPolicy.AccessPolicy.Expiry);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_IP, sharedAccessPolicy.AccessPolicy.IPAddressOrRange);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_PROTOCOL, sharedAccessPolicy.AccessPolicy.Protocols);
  }
  
  // add signature
  addIfNotNull(queryString, QueryStringConstants.SIGNATURE, this._generateAccountSharedAccessSignature(sharedAccessPolicy));

  return qs.stringify(queryString);
};


/**
* Generates the signature part of the shared access signature for a account.
* For more detailed information, refer to https://msdn.microsoft.com/en-us/library/azure/mt584140.aspx
*
* @param {object}                     sharedAccessPolicy                                The shared access policy.
* @param {SharedAccessServices}       sharedAccessPolicy.AccessPolicy.Services          The services (blob, file, queue, table) for a shared access signature associated with this shared access policy.
*                                                                                       Refer to `Constants.AccountSasConstants.Services`.
* @param {SharedAccessResourceTypes}  sharedAccessPolicy.AccessPolicy.ResourceTypes     The resource type for a shared access signature associated with this shared access policy.
*                                                                                       Refer to `Constants.AccountSasConstants.ResourceTypes`.
* @param {SharedAccessPermissions}    sharedAccessPolicy.AccessPolicy.Permissions       The permissions for a shared access signature. 
*                                                                                       Refer to `Constants.AccountSasConstants.Permissions`.
* @param {date}                       sharedAccessPolicy.AccessPolicy.Start             The time at which the Shared Access Signature becomes valid.
* @param {date}                       sharedAccessPolicy.AccessPolicy.Expiry            The time at which the Shared Access Signature becomes expired.
* @param {string}                     sharedAccessPolicy.AccessPolicy.IPAddressOrRange  An IP address or a range of IP addresses from which to accept requests. When specifying a range, note that the range is inclusive.
* @param {string}                     sharedAccessPolicy.AccessPolicy.Protocols         The protocols permitted for a request made with the account SAS. 
*                                                                                       Possible values are both HTTPS and HTTP (https,http) or HTTPS only (https). The default value is https,http. 
*                                                                                       Refer to `Constants.AccountSasConstants.Protocols`.
* @return {string} The signature part of the shared access signature.
*/
SharedKey.prototype._generateAccountSharedAccessSignature = function(sharedAccessPolicy){
  var getvalueToAppend = function (value, noNewLine) {
    var returnValue = '';
    if (!azureutil.objectIsNull(value)) {
      returnValue = value;
    }

    if (noNewLine !== true) {
      returnValue += '\n';
    }

    return returnValue;
  };  
  
  var stringToSign = getvalueToAppend(this.storageAccount) +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Permissions : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Services : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.ResourceTypes : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Start : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Expiry : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.IPAddressOrRange : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Protocols : '') +
      getvalueToAppend(HeaderConstants.TARGET_STORAGE_VERSION);
      
  return this.signer.sign(stringToSign);  
};

/**
* Signs a request with the Authentication header.
*
* @param {WebResource}      webResource The webresource to be signed.
* @param {function(error)}  callback    The callback function.
*/
SharedKey.prototype.signRequest = function (webResource, callback) {
  var getvalueToAppend = function (value, headerName) {
    // Do not sign content-length 0 in 2014-08-16 and later
    if (headerName === HeaderConstants.CONTENT_LENGTH && (azureutil.objectIsNull(value[headerName]) || value[headerName].toString() === '0')) {
      return '\n';
    } else if (azureutil.objectIsNull(value) || azureutil.objectIsNull(value[headerName])) {
      return '\n';
    } else {
      return value[headerName] + '\n';
    }
  };

  var stringToSign =
    webResource.method + '\n' +
    getvalueToAppend(webResource.headers, HeaderConstants.CONTENT_ENCODING) +
    getvalueToAppend(webResource.headers, HeaderConstants.CONTENT_LANGUAGE) +
    getvalueToAppend(webResource.headers, HeaderConstants.CONTENT_LENGTH) +
    getvalueToAppend(webResource.headers, HeaderConstants.CONTENT_MD5) +
    getvalueToAppend(webResource.headers, HeaderConstants.CONTENT_TYPE) +
    getvalueToAppend(webResource.headers, HeaderConstants.DATE) +
    getvalueToAppend(webResource.headers, HeaderConstants.IF_MODIFIED_SINCE) +
    getvalueToAppend(webResource.headers, HeaderConstants.IF_MATCH) +
    getvalueToAppend(webResource.headers, HeaderConstants.IF_NONE_MATCH) +
    getvalueToAppend(webResource.headers, HeaderConstants.IF_UNMODIFIED_SINCE) +
    getvalueToAppend(webResource.headers, HeaderConstants.RANGE) +
    this._getCanonicalizedHeaders(webResource) +
    this._getCanonicalizedResource(webResource);

  var signature = this.signer.sign(stringToSign);

  webResource.withHeader(HeaderConstants.AUTHORIZATION, 'SharedKey ' + this.storageAccount + ':' + signature);
  callback(null);
};

/*
* Retrieves the webresource's canonicalized resource string.
* @param {WebResource} webResource The webresource to get the canonicalized resource string from.
* @return {string} The canonicalized resource string.
*/
SharedKey.prototype._getCanonicalizedResource = function (webResource) {
  var path = '/';
  if (webResource.path) {
    path = webResource.path;
  }

  var canonicalizedResource = '/' + this.storageAccount + path;

  // Get the raw query string values for signing
  var queryStringValues = webResource.queryString;

  // Build the canonicalized resource by sorting the values by name.
  if (queryStringValues) {
    var paramNames = [];
    Object.keys(queryStringValues).forEach(function (n) {
      paramNames.push(n);
    });

    paramNames = paramNames.sort();
    Object.keys(paramNames).forEach(function (name) {
      canonicalizedResource += '\n' + paramNames[name] + ':' + queryStringValues[paramNames[name]];
    });
  }

  return canonicalizedResource;
};

/*
* Constructs the Canonicalized Headers string.
*
* To construct the CanonicalizedHeaders portion of the signature string,
* follow these steps: 1. Retrieve all headers for the resource that begin
* with x-ms-, including the x-ms-date header. 2. Convert each HTTP header
* name to lowercase. 3. Sort the headers lexicographically by header name,
* in ascending order. Each header may appear only once in the
* string. 4. Unfold the string by replacing any breaking white space with a
* single space. 5. Trim any white space around the colon in the header. 6.
* Finally, append a new line character to each canonicalized header in the
* resulting list. Construct the CanonicalizedHeaders string by
* concatenating all headers in this list into a single string.
*
* @param {object} The webresource object.
* @return {string} The canonicalized headers.
*/
SharedKey.prototype._getCanonicalizedHeaders = function (webResource) {
  // Build canonicalized headers
  var canonicalizedHeaders = '';
  if (webResource.headers) {
    var canonicalizedHeadersArray = [];
    for (var header in webResource.headers) {
      if (header.indexOf(HeaderConstants.PREFIX_FOR_STORAGE) === 0) {
        var headerItem = { canonicalized: header.toLowerCase(), original: header };
        canonicalizedHeadersArray.push(headerItem);
      }
    }

    canonicalizedHeadersArray.sort(function(a, b) { return a.canonicalized.localeCompare(b.canonicalized); });

    _.each(canonicalizedHeadersArray, function (currentHeaderItem) {
      var value = webResource.headers[currentHeaderItem.original];
      if (!azureutil.IsNullOrEmptyOrUndefinedOrWhiteSpace(value)) {
        canonicalizedHeaders += currentHeaderItem.canonicalized + ':' + value + '\n';
      } else {
        canonicalizedHeaders += currentHeaderItem.canonicalized + ':\n';
      }
    });
  }

  return canonicalizedHeaders;
};

/**
* Generates the query string for a shared access signature signing.
*
* @this {SharedAccessSignature}
* @param {string}                     serviceType                                         The service type.
* @param {string}                     path                                                The path to the resource.
* @param {object}                     sharedAccessPolicy                                  The shared access policy.
* @param {string}                     [sharedAccessPolicy.Id]                             The signed identifier.
* @param {SharedAccessPermissions}    [sharedAccessPolicy.AccessPolicy.Permissions]       The permission type.
* @param {date}                       [sharedAccessPolicy.AccessPolicy.Start]             The time at which the Shared Access Signature becomes valid.
* @param {date}                       [sharedAccessPolicy.AccessPolicy.Expiry]            The time at which the Shared Access Signature becomes expired.
* @param {string}                     [sharedAccessPolicy.AccessPolicy.IPAddressOrRange]  An IP address or a range of IP addresses from which to accept requests. When specifying a range, note that the range is inclusive.
* @param {string}                     [sharedAccessPolicy.AccessPolicy.Protocols]         The protocols permitted for a request made with the account SAS. 
*                                                                                         Possible values are both HTTPS and HTTP (https,http) or HTTPS only (https). The default value is https,http. 
* @param {string}                     sasVersion                                          A string indicating the desired SAS Version to use, in storage service version format. Value must be 2012-02-12 or later.
* @parma {ResourceTypes}              [args.resourceType]                                 The resource type, if the resource is a blob or container.  Null if the resource is a queue or table.
* @parma {ResourceTypes}              [args.tableName]                                    The table name, if the resource is a table.  Null if the resource is a blob orqueue.
* @parma {ResourceTypes}              [args.queryString]                                  The query string, if additional parameters are desired.
* @param {object}                     [args.headers]                                      The optional header values to set for a blob returned wth this SAS.
* @param {string}                     [args.headers.CacheControl]                         The value of the Cache-Control response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentType]                          The value of the Content-Type response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentEncoding]                      The value of the Content-Encoding response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentLanguage]                      The value of the Content-Language response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentDisposition]                   The value of the Content-Disposition response header to be returned when this SAS is used.
* @return {object} The shared access signature query string.
*/
SharedKey.prototype.generateSignedQueryString = function (serviceType, path, sharedAccessPolicy, sasVersion, args) {
  var addIfNotNull = function (queryString, name, value) {
    if (!azureutil.objectIsNull(name) && !azureutil.objectIsNull(value)) {
      queryString[name] = value;
    }
  };

  var validateVersion = function (sasVersion) {
    // validate and add version
    if (azureutil.objectIsNull(sasVersion)) {
      return HeaderConstants.TARGET_STORAGE_VERSION;
    } else {
      var values = _.values(CompatibleVersionConstants);
      if (values.some(function(version) {
        return version.toLowerCase() === sasVersion.toLowerCase();
      })) {
        return sasVersion;
      } else {
        throw new ArgumentError('sasVersion', azureutil.stringFormat(SR.INVALID_SAS_VERSION, sasVersion, values));
      }
    }
  };

  var formatAccessPolicyDates = function (accessPolicy) {
    if (!azureutil.objectIsNull(accessPolicy.Start)) {
      if (!_.isDate(accessPolicy.Start)) {
        accessPolicy.Start = new Date(accessPolicy.Start);
      }
 
      accessPolicy.Start = azureutil.truncatedISO8061Date(accessPolicy.Start);
    }

    if (!azureutil.objectIsNull(accessPolicy.Expiry)) {
      if (!_.isDate(accessPolicy.Expiry)) {
        accessPolicy.Expiry = new Date(accessPolicy.Expiry);
      }

      accessPolicy.Expiry = azureutil.truncatedISO8061Date(accessPolicy.Expiry);
    }
  };

  // set up optional args
  var queryString;
  var resourceType;
  var headers;
  var tableName;
  
  if(args) {
    queryString = args.queryString;
    resourceType = args.resourceType;
    tableName = args.tableName;
    headers = args.headers;
  }

  if(!queryString) {
    queryString = {};
  }

  // add shared access policy params
  if (sharedAccessPolicy.AccessPolicy) {
    formatAccessPolicyDates(sharedAccessPolicy.AccessPolicy);

    addIfNotNull(queryString, QueryStringConstants.SIGNED_START, sharedAccessPolicy.AccessPolicy.Start);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_EXPIRY, sharedAccessPolicy.AccessPolicy.Expiry);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_PERMISSIONS, sharedAccessPolicy.AccessPolicy.Permissions);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_IP, sharedAccessPolicy.AccessPolicy.IPAddressOrRange);
    addIfNotNull(queryString, QueryStringConstants.SIGNED_PROTOCOL, sharedAccessPolicy.AccessPolicy.Protocols);

    // tables only
    addIfNotNull(queryString, QueryStringConstants.STARTPK, sharedAccessPolicy.AccessPolicy.StartPk);
    addIfNotNull(queryString, QueryStringConstants.ENDPK, sharedAccessPolicy.AccessPolicy.EndPk);
    addIfNotNull(queryString, QueryStringConstants.STARTRK, sharedAccessPolicy.AccessPolicy.StartRk);
    addIfNotNull(queryString, QueryStringConstants.ENDRK, sharedAccessPolicy.AccessPolicy.EndRk);
  }

  // validate and add version
  var validatedSASVersionString = validateVersion(sasVersion);
  addIfNotNull(queryString, QueryStringConstants.SIGNED_VERSION, validatedSASVersionString);

  // add signed identifier
  addIfNotNull(queryString, QueryStringConstants.SIGNED_IDENTIFIER, sharedAccessPolicy.Id);

  // blobs only
  addIfNotNull(queryString, QueryStringConstants.SIGNED_RESOURCE, resourceType);
  if (headers) {
    addIfNotNull(queryString, QueryStringConstants.CACHE_CONTROL, headers.cacheControl);
    addIfNotNull(queryString, QueryStringConstants.CONTENT_TYPE, headers.contentType);
    addIfNotNull(queryString, QueryStringConstants.CONTENT_ENCODING, headers.contentEncoding);
    addIfNotNull(queryString, QueryStringConstants.CONTENT_LANGUAGE, headers.contentLanguage);
    addIfNotNull(queryString, QueryStringConstants.CONTENT_DISPOSITION, headers.contentDisposition);
  }

  // tables only
  addIfNotNull(queryString, QueryStringConstants.TABLENAME, tableName);

  // add signature
  addIfNotNull(queryString, QueryStringConstants.SIGNATURE, this._generateSignature(serviceType, path, sharedAccessPolicy, validatedSASVersionString, {resourceType: resourceType, headers: headers, tableName: tableName}));

  return qs.stringify(queryString);
};

/**
* Generates the shared access signature for a resource.
*
* @this {SharedAccessSignature}
* @param {string}                     serviceType                                         The service type.
* @param {string}                     path                                                The path to the resource.
* @param {object}                     sharedAccessPolicy                                  The shared access policy.
* @param {string}                     [sharedAccessPolicy.Id]                             The signed identifier.
* @param {SharedAccessPermissions}    [sharedAccessPolicy.AccessPolicy.Permissions]       The permission type.
* @param {date}                       [sharedAccessPolicy.AccessPolicy.Start]             The time at which the Shared Access Signature becomes valid.
* @param {date}                       [sharedAccessPolicy.AccessPolicy.Expiry]            The time at which the Shared Access Signature becomes expired.
* @param {string}                     [sharedAccessPolicy.AccessPolicy.IPAddressOrRange]  An IP address or a range of IP addresses from which to accept requests. When specifying a range, note that the range is inclusive.
* @param {string}                     [sharedAccessPolicy.AccessPolicy.Protocols]         The protocols permitted for a request made with the account SAS. 
*                                                                                         Possible values are both HTTPS and HTTP (https,http) or HTTPS only (https). The default value is https,http.
* @param {string}                     sasVersion                                          A string indicating the desired SAS Version to use, in storage service version format. Value must be 2012-02-12 or later.
* @parma {ResourceTypes}              [args.resourceType]                                 The resource type, if the resource is a blob or container.  Null if the resource is a queue or table.
* @parma {ResourceTypes}              [args.tableName]                                    The table name, if the resource is a table.  Null if the resource is a blob or queue.
* @param {object}                     [args.headers]                                      The optional header values to set for a blob returned wth this SAS.
* @param {string}                     [args.headers.CacheControl]                         The value of the Cache-Control response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentType]                          The value of the Content-Type response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentEncoding]                      The value of the Content-Encoding response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentLanguage]                      The value of the Content-Language response header to be returned when this SAS is used.
* @param {string}                     [args.headers.ContentDisposition]                   The value of the Content-Disposition response header to be returned when this SAS is used.
* @return {string} The shared access signature.
*/
SharedKey.prototype._generateSignature = function (serviceType, path, sharedAccessPolicy, sasVersion, args) {
  var getvalueToAppend = function (value, noNewLine) {
    var returnValue = '';
    if (!azureutil.objectIsNull(value)) {
      returnValue = value;
    }

    if (noNewLine !== true) {
      returnValue += '\n';
    }

    return returnValue;
  };

  // set up optional args
  var resourceType;
  var tableName;
  var headers;
  if(args) {
    resourceType = args.resourceType;
    tableName = args.tableName;
    headers = args.headers;
  }

  // Add leading slash to path
  if (path.substr(0, 1) !== '/') {
    path = '/' + path;
  }

  var canonicalizedResource;
  if (sasVersion === CompatibleVersionConstants.FEBRUARY_2012 || sasVersion === CompatibleVersionConstants.AUGUST_2013) {
    // Do not prepend service name for older versions
    canonicalizedResource = '/' + this.storageAccount + path;
  } else {
    canonicalizedResource = '/' + serviceType + '/' + this.storageAccount + path;
  }

  var stringToSign = getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Permissions : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Start : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Expiry : '') +
      getvalueToAppend(canonicalizedResource) +
      getvalueToAppend(sharedAccessPolicy.Id) +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.IPAddressOrRange : '') +
      getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.Protocols : '') +
      sasVersion;

  if(sasVersion == CompatibleVersionConstants.FEBRUARY_2012) {
    if(headers) {
      throw new ArgumentError('args.headers', SR.INVALID_HEADERS);
    }
  } else if (resourceType) {
    stringToSign += '\n' +
    getvalueToAppend(headers ? headers.cacheControl : '') +
    getvalueToAppend(headers ? headers.contentDisposition : '') +
    getvalueToAppend(headers ? headers.contentEncoding : '') +
    getvalueToAppend(headers ? headers.contentLanguage : '') +
    getvalueToAppend(headers ? headers.contentType : '', true);
  }

  if(tableName) {
    stringToSign += '\n' + 
    getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.StartPk : '') +
    getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.StartRk : '') +
    getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.EndPk : '') +
    getvalueToAppend(sharedAccessPolicy.AccessPolicy ? sharedAccessPolicy.AccessPolicy.EndRk : '', true);
  }

  return this.signer.sign(stringToSign);
};

module.exports = SharedKey;