Last active
February 9, 2024 15:40
-
-
Save Leigh-/a2798584b79fd9072605a4cc7ff60df4 to your computer and use it in GitHub Desktop.
Amazon Web Services Signature 4 Utility for ColdFusion (Alpha)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Amazon Web Services Signature 4 Utility for ColdFusion | |
* Version Date: 2016-04-12 (Alpha) | |
* | |
* Copyright 2016 Leigh (cfsearching) | |
* | |
* Requirements: Adobe ColdFusion 10+ | |
* AWS Signature 4 specifications: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html | |
* | |
* 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. | |
*/ | |
component { | |
/** | |
* Creates a new instance of the utility for generating signatures using the supplied settings | |
* | |
* @awsAccessKeyId AWS Access Key Id | |
* @awsSecretAccessKey AWS secret Key | |
* @defaultRegionName (Optional) Sets a default region for all requests made through this instance. This setting can be overriden at the request level in generateSignatureData() | |
* @defaultServiceName (Optional) Sets a default service name for all requests made through this instance. This setting can be overriden at the request level in generateSignatureData() | |
* @returns new instance initalized with specified settings | |
*/ | |
Sv4 function init( | |
required string accessKeyId | |
, required string secretAccessKey | |
, string defaultRegionName = "" | |
, string defaultServiceName = "" | |
){ | |
// Store AWS keys and settings | |
variables.accessKeyId = arguments.accessKeyId; | |
variables.secretAccessKey = arguments.secretAccessKey; | |
variables.defaultRegionName = arguments.defaultRegionName; | |
variables.defaultServiceName = arguments.defaultServiceName; | |
// Algorithms used in calculating the signature | |
variables.signatureAlgorithm = "AWS4-HMAC-SHA256"; | |
variables.hashAlorithm = "SHA256"; | |
return this; | |
} | |
/** | |
* Generates Signature 4 properties for the supplied request settings. | |
* | |
* @requestMethod - Request operation, ie PUT, GET, POST, etcetera. | |
* @hostName - Target host name, example: bucketname.s3.amazonaws.com | |
* @requestURI - Absolute path of the URI. Portion of the URL after the host, to the "?" beginning the query string | |
* @requestBody - Body of the request. Either a string or binary value. | |
* @requestHeaders - Structure of http headers for used the request. Mandatory host and date headers are automatically generated. | |
* @requestParams - Structure containing any url parameters for the request. Mandatory parameters are automatically generated. | |
* @signedPayload - If true, include hash of requestPayload in signature calculations. Otherwise, literal "UNSIGNED-PAYLOAD". Default is true. | |
* @excludeHeaders - (Optional) List of header names AWS can exclude from the signing process. Default is an empty array, which means all headers should be "signed" | |
* @amzDate - (Optional) Override the automatic X-Amz-Date calculation with this value. Current UTC date. If supplied, @dateStamp is required. Format: yyyyMMddTHHnnssZ | |
* @regionName - (Optional) Override the instance region name with this value. Example "us-east-1" | |
* @serviceName - (Optional) Override the instance service name with this value. Example "s3" | |
* @dateStamp - (Optional) Override the automatic dateStamp calculation with this value. Current UTC date (only). If supplied, @amzDate is required. Format: yyyyMMdd | |
* @returns Signature value, authorization header and all properties part of the signature calculation: ALGORITHM,AMZDATE,AUTHORIZATIONHEADER,CANONICALHEADERS,CANONICALQUERYSTRING,CANONICALREQUEST,CANONICALURI,CREDENTIALSCOPE,DATESTAMP,EXCLUDEHEADERS,HOSTNAME,REGIONNAME,REQUESTHEADERS,REQUESTMETHOD,REQUESTPARAMS,REQUESTPAYLOAD,SERVICENAME,IGNATURE,SIGNEDHEADERS,SIGNKEYBYTES,STRINGTOSIGN | |
* | |
*/ | |
public struct function generateSignatureData( | |
required string requestMethod | |
, required string hostName | |
, required string requestURI | |
, required any requestBody | |
, required struct requestHeaders | |
, required struct requestParams | |
, boolean signedPayload = true | |
, array excludeHeaders = [] | |
, string regionName | |
, string serviceName | |
, string amzDate | |
, string dateStamp | |
) { | |
// Initialize properties | |
var props = {}; | |
var headerNames = ''; | |
var hasQueryParams = structCount(arguments.requestParams) > 0; | |
var utcDateTime = dateConvert("local2UTC", now()); | |
// Generate UTC time stamps | |
props.dateStamp = dateFormat( utcDateTime, "YYYYMMDD" ); | |
props.amzDate = props.dateStamp &"T"& timeFormat(utcDateTime, "HHnnssZ"); | |
// Override current utc date and time | |
if (structKeyExists(arguments, "amzDate") || structKeyExists(arguments, "dateStamp")) { | |
props.dateStamp = arguments.dateStamp; | |
props.amzDate = arguments.amzDate; | |
} | |
// Apply instance level region/service name settings | |
props.regionName = variables.defaultRegionName; | |
props.serviceName = variables.defaultServiceName; | |
// Override instance level region/service names | |
if (structKeyExists(arguments, "regionName")) { | |
props.regionName = arguments.regionName; | |
} | |
if (structKeyExists(arguments, "serviceName")) { | |
props.serviceName = arguments.serviceName; | |
} | |
///////////////////////////////////// | |
// Basic request properties | |
///////////////////////////////////// | |
props.algorithm = variables.signatureAlgorithm; | |
props.hostName = arguments.hostName; | |
props.requestMethod = arguments.requestMethod; | |
props.canonicalURI = buildCanonicalURI( requestURI = arguments.requestURI ); | |
// For signed requests, the payload is a checksum | |
props.requestPayload = arguments.signedPayload ? hash256( arguments.requestBody ) : arguments.requestBody ; | |
props.credentialScope = buildCredentialScope( dateStamp=props.dateStamp, serviceName=props.serviceName, regionName=props.regionName ); | |
///////////////////////////////////// | |
// Validate headers/parameters | |
///////////////////////////////////// | |
props.requestHeaders = duplicate( arguments.requestHeaders ); | |
props.requestParams = duplicate( arguments.requestParams ); | |
// Host header is mandatory for ALL requests | |
props.requestHeaders["Host"] = arguments.hostName; | |
// Signed requests must include a checksum, ie hash of payload | |
if (arguments.signedPayload) { | |
props.requestHeaders["X-Amz-Content-Sha256"] = props.requestPayload; | |
} | |
// Apply mandatory headers and parameters | |
if (hasQueryParams) { | |
// First, normalize request headers | |
props.requestHeaders = cleanHeaders( props.requestHeaders ); | |
props.excludeHeaders = cleanHeaderNames( arguments.excludeHeaders ); | |
// Identify which headers will be included in the signing process | |
props.signedHeaders = buildSignedHeaders( requestHeaders=props.requestHeaders, excludeNames=props.excludeHeaders ); | |
// When passing all parameters in query string, canonical query string must also | |
// include the parameters used as part of the signing process, ie hashing algorithm, | |
// credential scope, date, and signed headers parameters. | |
props.requestParams["X-Amz-Algorithm"] = variables.signatureAlgorithm; | |
props.requestParams["X-Amz-Credential"] = variables.accessKeyId &"/"& props.credentialScope; | |
props.requestParams["X-Amz-SignedHeaders"] = props.signedHeaders; | |
props.requestParams["X-Amz-Date"] = props.amzDate; | |
// Finally, normalize url parameters | |
props.requestParams = encodeQueryParams( queryParams=props.requestParams ); | |
} | |
// All other request types (PUT, DELETE, POST, ....) | |
else { | |
// Host header is mandatory for ALL requests | |
props.requestHeaders["Host"] = arguments.hostName; | |
// Date header is mandatory when not passing values in url | |
props.requestHeaders["X-Amz-Date"] = props.amzDate; | |
// For signed requests, include a checksum header, ie hash of payload | |
if (arguments.signedPayload) { | |
props.requestHeaders["X-Amz-Content-Sha256"] = props.requestPayload; | |
} | |
// Normalize headers and url parameters | |
props.requestHeaders = cleanHeaders( props.requestHeaders ); | |
props.excludeHeaders = cleanHeaderNames( arguments.excludeHeaders ); | |
// Identify which headers will be included in the signing process | |
props.signedHeaders = buildSignedHeaders( requestHeaders=props.requestHeaders, excludeNames=props.excludeHeaders ); | |
props.requestParams = encodeQueryParams( queryParams=props.requestParams ); | |
} | |
///////////////////////////////////////// | |
// Generate signature | |
///////////////////////////////////////// | |
// Generate header, query, and request strings | |
props.canonicalQueryString = buildCanonicalQueryString( requestParams=props.requestParams ); | |
props.canonicalHeaders = buildCanonicalHeaders( requestHeaders=props.requestHeaders ); | |
props.canonicalRequest = buildCanonicalRequest( argumentCollection=props ); | |
// Generate signature and authorization strings | |
props.stringToSign = generateStringToSign( argumentCollection=props ); | |
props.signKeyBytes = generateSignatureKey( argumentCollection=props ); | |
props.signature = lcase( binaryEncode( hmacBinary( message=props.stringToSign, key=props.signKeyBytes), "hex") ); | |
props.authorizationHeader = buildAuthorizationHeader( argumentCollection=props ); | |
// (Debugging) Convert binary values into human readable form | |
props.signKeyBytes = binaryEncode( props.signKeyBytes, "hex" ); | |
return props; | |
} | |
/** | |
* Generates request string to sign | |
* | |
* @amzDate - Current timestamp in UTC. Format yyyyMMddTHHnnssZ | |
* @credentialScope - String defining scope of request. See buildCredentialScope(). | |
* @canonicalRequest - Canonical request string | |
* @returns - String to be signed | |
*/ | |
private string function generateStringToSign( | |
required string amzDate | |
, required string credentialScope | |
, required string canonicalRequest | |
) { | |
// Format: Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HashedCanonicalRequest | |
var elements = [ variables.signatureAlgorithm | |
, arguments.amzDate | |
, arguments.credentialScope | |
, hash256( arguments.canonicalRequest ) | |
]; | |
return arrayToList( elements, chr(10) ); | |
} | |
/** | |
* Generate canonical request string | |
* | |
* @requestMethod - Request operation, ie PUT, GET, POST, etcetera. | |
* @canonicalURI - Canonical URL string. See buildCanonicalURI | |
* @canonicalHeaders - Canonical header string. See buildCanonicalHeaders | |
* @canonicalQueryString - Canonical query string. See buildCanonicalQueryString | |
* @signedHeaders - List of signed headers. See buildSignedHeaders | |
* @requestPayload - For signed requests, this is the hash of the request body. Otherwise, the raw request body | |
*/ | |
private string function buildCanonicalRequest( | |
required string requestMethod | |
, required string canonicalURI | |
, required string canonicalQueryString | |
, required string canonicalHeaders | |
, required string signedHeaders | |
, required string requestPayload ){ | |
var canonicalRequest = ""; | |
// Build ordered list of elements in the request, delimited by new lines | |
// Note: Headers and signed headers should never be empty. "Host" header is always required. | |
canonicalRequest = arguments.requestMethod & chr(10) | |
& arguments.canonicalURI & chr(10) | |
& arguments.canonicalQueryString & chr(10) | |
& arguments.canonicalHeaders & chr(10) | |
& arguments.signedHeaders & chr(10) | |
& arguments.requestPayload ; | |
return canonicalRequest; | |
} | |
/** | |
* Generates canonical query string | |
* <ul> | |
* <li>URI-encode each parameter name and value according to RFC 3986 </li> | |
* <li>Percent-encode all other characters with %XY, where X and Y are hexadecimal characters (0-9 and uppercase A-F) </li> | |
* <li>Sort the encoded parameter names by character code in ascending order (ASCII order) </li> | |
* <li>Build the canonical query string by starting with the first parameter name in the sorted list. </li> | |
* <li>For each parameter, append the URI-encoded parameter name, followed by the character '=' (ASCII code 61), followed by the URI-encoded parameter value. Use an empty string for parameters that have no value. </li> | |
* <li>Append the character '&' (ASCII code 38) after each parameter value, except for the last value in the list. </li> | |
* </ul> | |
* | |
* @requestParams Structure containing all parameters passed via the query string. | |
* @isEncoded If true, the supplied parameters are already url encoded | |
* @returns canonical query string | |
*/ | |
private string function buildCanonicalQueryString(required struct requestParams, boolean isEncoded = true) { | |
var encodedParams = ""; | |
var paramNames = ""; | |
var paramPairs = ""; | |
// Ensure parameter names and values are URL encoded first | |
encodedParams = isEncoded ? arguments.requestParams : encodeQueryParams( arguments.requestParams ); | |
// Extract and sort encoded parameter names | |
paramNames = structKeyArray( encodedParams ); | |
arraySort( paramNames, "text", "asc" ); | |
// Build array of sorted name/value pairs | |
paramPairs = []; | |
arrayEach( paramNames, function(string param) { | |
arrayAppend( paramPairs, arguments.param &"="& encodedParams[ arguments.param ] ); | |
}); | |
// Finally, generate sorted list of parameters, delimited by "&" | |
return arrayToList(paramPairs, "&"); | |
} | |
/** | |
* Generates a list of signed header names. | |
* | |
* <p>"...By adding this list of headers, you tell AWS which headers in the request | |
* are part of the signing process and which ones AWS can ignore (for example, any | |
* additional headers added by a proxy) for purposes of validating the request."</p> | |
* | |
* @requestHeaders Raw headers to be included in request | |
* @excludeNames Names of any headers AWS should ignore for the signing process | |
* @returns Sorted list of signed header names, delimited by semi-colon ";" | |
*/ | |
private string function buildSignedHeaders(required struct requestHeaders, required array excludeNames ) { | |
var name = ""; | |
var headerNames = []; | |
var allHeaders = !arrayLen(arguments.excludeNames); | |
// Identify which headers are "signed" | |
structEach( arguments.requestHeaders, function(string name, any value) { | |
if (allHeaders || !arrayFindNoCase( excludeNames, arguments.name)) { | |
arrayAppend( headerNames, arguments.name ); | |
} | |
}); | |
// Sort header names in ASCII order | |
arraySort( headerNames, "text", "asc" ); | |
// Return list of names | |
return arrayToList( headerNames, ";" ); | |
} | |
/** | |
* Generates a list of canonical headers | |
* @requestHeaders Structure containing headers to be included in request hash | |
* @returns Sorted list of header pairs, delimited by new lines | |
*/ | |
private string function buildCanonicalHeaders(required struct requestHeaders ) { | |
var pairs = ""; | |
var names = ""; | |
var headers = ""; | |
// Scrub the header names and values first | |
headers = cleanHeaders( arguments.requestHeaders ); | |
// Sort header names in ASCII order | |
names = structKeyArray( headers ); | |
arraySort( names, "text", "asc" ); | |
// Build array of sorted header name and value pairs | |
pairs = []; | |
arrayEach( names, function(string key) { | |
arrayAppend( pairs, arguments.key &":"& headers[ arguments.key ] ); | |
}); | |
// Generate list. Note: List must END WITH a new line character | |
return arrayToList( pairs, chr(10)) & chr(10); | |
} | |
/** | |
* Generates canonical URI. Encoded, absolute path component of the URI, | |
* which is everything in the URI from the HTTP host to the question mark character ("?") | |
* that begins the query string parameters (if any) | |
* @uriPath URI or path. If empty, "/" will be used | |
* @returns URL encoded path | |
*/ | |
private string function buildCanonicalURI(required string requestURI) { | |
var path = arguments.requestURI; | |
// Return "/" for empty path | |
if (!len(trim(path))) { | |
path = "/"; | |
} | |
// Convert to absolute path (if needed) | |
if (left(path, 1) != "/") { | |
path = "/"& path; | |
} | |
// Encode path, but preserve slashes "/" | |
path = replace( urlEncode( path ), "%2F", "/", "all"); | |
return path; | |
} | |
/** | |
* Generates signing key for AWS Signature V4 | |
* | |
* <p>Source: http://stackoverflow.com/questions/32513197/how-to-derive-a-sign-in-key-for-aws-signature-version-4-in-coldfusion</p> | |
* | |
* @dateStamp Date stamp in YYYYMMDD format. Example: 20150830 | |
* @regionName Region name that is part of the service's endpoint (alphanumeric). Example: "us-east-1" | |
* @serviceName Service name that is part of the service's endpoint (alphanumeric). Example: "s3" | |
* @algorithm HMAC algorithm. Default is "HMACSHA256" | |
* @returns signing key in binary | |
*/ | |
private binary function generateSignatureKey( | |
required string dateStamp | |
, required string regionName | |
, required string serviceName | |
, string algorithm = "HMACSHA256" | |
){ | |
var kSecret = charsetDecode("AWS4" & variables.secretAccessKey, "UTF-8"); | |
var kDate = hmacBinary( arguments.dateStamp, kSecret ); | |
// Region information as a lowercase alphanumeric string | |
var kRegion = hmacBinary( lcase(arguments.regionName), kDate ); | |
// Service name information as a lowercase alphanumeric string | |
var kService = hmacBinary( lcase(arguments.serviceName), kRegion ); | |
// A special termination string: aws4_request | |
var kSigning = hmacBinary( "aws4_request", kService ); | |
return kSigning; | |
} | |
/** | |
* Generates string indicating the scope for which the signature is valid. Credential scope | |
* is represented by a slash-separated string of dimensions in the following order: | |
* | |
* dateStamp / regionName / serviceName / terminationString | |
* | |
* @dateStamp - Current date in UTC (must be same as X-Amz-Date date). Format yyyyMMdd | |
* @regionName - Name of the target region, UTF-8 encoded. Example "us-east-1" | |
* @serviceName - Name of the target service, UTF-8 encoded. Example "s3" | |
* @returns - formatted string. Example: 20150830/us-east-1/iam/aws4_request | |
*/ | |
private string function buildCredentialScope( | |
required string dateStamp | |
, required string regionName | |
, required string serviceName | |
) { | |
return arguments.dateStamp &"/"& lcase(arguments.regionName) &"/"& lcase(arguments.serviceName) &"/"& "aws4_request"; | |
} | |
/** | |
* Generates Authorization header string. | |
* | |
* Format: algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope | |
* + ', ' + 'SignedHeaders=' + signed_headers + ', ' | |
* + 'Signature=' + signature | |
* | |
* @dateStamp - Current date in UTC (must be same as X-Amz-Date date). Format yyyyMMdd | |
* @regionName - Name of the target region, UTF-8 encoded. Example "us-east-1" | |
* @serviceName - Name of the target service, UTF-8 encoded. Example "s3" | |
* @returns - formatted string. Example: 20150830/us-east-1/iam/aws4_request | |
*/ | |
private string function buildAuthorizationHeader( | |
required struct requestHeaders | |
, required string signedHeaders | |
, required string credentialScope | |
, required string signature | |
) { | |
var authHeader = variables.signatureAlgorithm &" " | |
& "Credential=" & variables.accessKeyId &"/"& arguments.credentialScope & ", " | |
& "SignedHeaders=" & arguments.signedHeaders & ", " | |
& "Signature="& arguments.signature; | |
return authHeader; | |
} | |
/** | |
* Generates string indicating the scope for which the signature is valid | |
* | |
* @dateStamp - Current date in UTC (must be same as X-Amz-Date date). Format yyyyMMdd | |
* @regionName - Name of the target region, UTF-8 encoded. Example "us-east-1" | |
* @serviceName - Name of the target service, UTF-8 encoded. Example "s3" | |
* @returns - Credential header string. Example: 20150830/us-east-1/iam/aws4_request | |
*/ | |
private string function buildCredentialString( | |
required string dateStamp | |
, required string regionName | |
, required string serviceName | |
){ | |
return variables.accessKeyId &"/"& buildCredentialScope( argumentCollection=arguments ); | |
} | |
/** | |
* Convenience method which generates a (binary) HMAC code for the specified message | |
* | |
* @message Message to sign | |
* @key HMAC key in binary form | |
* @algorithm Signing algorithm. [ Default is "HMACSHA256" ] | |
* @encoding Character encoding of message string. [ Default is UTF-8 ] | |
* @returns HMAC value for the specified message as binary (currently unsupported in CF11) | |
*/ | |
private binary function hmacBinary ( | |
required string message | |
, required binary key | |
, string algorithm = "HMACSHA256" | |
, string encoding = "UTF-8" | |
){ | |
// Generate HMAC and decode result into binary | |
return binaryDecode( HMAC( arguments.message, arguments.key, arguments.algorithm, arguments.encoding), "hex" ); | |
} | |
/** | |
* Convenience method that hashes the supplied value, with SHA256 | |
* @text value to hash | |
* @returns hashed value, in lower case | |
*/ | |
private string function hash256 ( required any text ){ | |
return lcase( hash(arguments.text, "SHA256") ); | |
} | |
/** | |
* URL encode query parameters and names | |
* @params Structure containing all query parameters for the request | |
* @returns new structure with all parameter names and values encoded | |
*/ | |
private struct function encodeQueryParams(required struct queryParams) { | |
// First encode parameter names and values | |
var encodedParams = {}; | |
structEach( arguments.queryParams, function(string key, string value) { | |
encodedParams[ urlEncode(arguments.key) ] = urlEncode( arguments.value ); | |
}); | |
return encodedParams; | |
} | |
/** | |
* Scrubs header names and values: | |
* <ul> | |
* <li>Removes leading and trailing spaces from names and values</li> | |
* <li>Converts sequential spaces to single space in names and values</li> | |
* <li>Converts all header names to lower case</li> | |
* </ul> | |
* @headers Header names and values to scrub | |
* @returns structure of parsed header names and values | |
*/ | |
private struct function cleanHeaders(required struct headers) { | |
var headerName = ""; | |
var headerValue = ""; | |
var cleaned = {}; | |
structEach( arguments.headers, function(string key, string value) { | |
headerName = cleanHeader( arguments.key ); | |
headerValue = cleanHeader( arguments.value ); | |
cleaned[ lcase( headerName ) ] = headerValue; | |
}); | |
return cleaned; | |
} | |
/** | |
* Scrubs header names and values: | |
* <ul> | |
* <li>Removes leading and trailing spaces</li> | |
* <li>Converts sequential spaces to single space</li> | |
* <li>Converts all names to lower case</li> | |
* </ul> | |
* @headers Header names to scrub | |
* @returns array of parsed header names | |
*/ | |
private array function cleanHeaderNames(required array names) { | |
var headerName = ""; | |
var cleaned = []; | |
arrayEach( names, function(string headerName) { | |
arrayAppend( cleaned, cleanHeader( arguments.headerName ) ); | |
}); | |
return cleaned; | |
} | |
/** | |
* Removes extraneous white space from header names or values. | |
* See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html | |
* | |
* <ul> | |
* <li>Removes leading and trailing spaces</li> | |
* <li>Converts sequential spaces to single space</li> | |
* </ul> | |
* @text Text to scrub | |
* @returns parsed text | |
*/ | |
private string function cleanHeader(required string text) { | |
return reReplace( trim( arguments.text ), "\s+", chr(32), "all" ); | |
} | |
/** | |
* URL encodes the supplied string per RFC 3986, which defines the following as | |
* unreserved characters that should NOT be encoded: | |
* | |
* A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ). | |
* | |
* @value string to encode | |
* @returns URI encoded string | |
*/ | |
private string function urlEncode( string value ) { | |
var encodedValue = encodeForURL(arguments.value); | |
// Reverse encoding of tilde "~" | |
encodedValue = replace( encodedValue, encodeForURL("~"), "~", "all" ); | |
// Fix encoding of spaces, ie replace '+' into "%20" | |
encodedValue = replace( encodedValue, "+", "%20", "all" ); | |
// Asterisk "*" should be encoded | |
encodedValue = replace( encodedValue, "*", "%2A", "all" ); | |
return encodedValue; | |
} | |
/** | |
* Returns current UTC date and time in the following formats: | |
* - dateStamp - Current UTC date, format: YYYYMMDD | |
* - timeStamp - Current UTC date and time, format: YYYYMMDDTHHnnssZ | |
* @returns structure containing date and time strings | |
*/ | |
public struct function getUTCStrings() { | |
var utcDateTime = dateConvert("local2UTC", now()); | |
var result = {}; | |
// Generate UTC time stamps | |
result.dateStamp = dateFormat( utcDateTime, "YYYYMMDD" ); | |
result.amzDate = result.dateStamp &"T"& timeFormat(utcDateTime, "HHnnssZ"); | |
result.timeStamp = dateFormat( utcDateTime, "YYYY-MM-DD") &"T"& timeFormat(utcDateTime, "HH:nn:ssZ"); | |
return result; | |
} | |
} |
The above code has some issues. I have resolved it.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
getting error
{ "errors": [ { "message": "The request signature we calculated does not match the signature you provided.