Related
I am almost there for getting the CoinBase API Sorted - but missing something or a bad conversion. I have tried a few things. This is best I can get too for building everything. I even setup a brand new key, in case old key.
I get a response from CoinBase : {"message":"invalid signature"}
If I purposely error out Key, Passphrase, Timestampe : it identifies those as incorrect. So all is right except this signature bit. I've included the Coinbase API Signature Details as well.
<cfset base_api = "https://api.exchange.coinbase.com/orders">
<cfset req_path = "/orders">
<cfset cb_key = "9999ac99999e6e99ee10dfd8ea5f9999">
<cfset cb_s = "999a7S/JZVX09EAX/LvWz9999/ALnVQptso999999dxrVfXfd999993OXAlfdPGwGZUKPBa99999pg1ubhVlsw==">
<cfset cb_pass = "999957agr99">
<cfset pair = "SHIB-USDT">
<cfset side = "sell">
<cfset type = "market">
<cfset size = "79940">
<cfset startDate = createdatetime( '1970','01','01','00','00','00' )>
<cfset datetimeNow = dateConvert( "local2Utc", now() )>
<cfset gmtnow = #DateAdd("h", 7, datetimeNow)#>
<cfset UnixStamp = datediff( 's', startdate, gmtnow )>
<cfscript>
cbs = #cb_s#;
body = SerializeJSON({
size: '#size#',
type: '#type#',
side: '#side#',
product_id: '#pair#'});
method = 'POST';
// create the prehash string by concatenating required parts
message = #UnixStamp# & method & #req_path# & body;
// decode the base64 secret
key = toBinary( #cb_s# );
// create sha256 hmac with the key
theHmac = hmac("#message#","#Key#", "HMACSHA256");
// encode the result
cas = toBase64(theHmac);
</cfscript>
<cfoutput>
<br>#cbs#<br>
<b>#cas#</b><br><br>
<br>#message#
</cfoutput>
<cfhttp url="#base_api#" method="post" result="result" charset="utf-8">
<cfhttpparam type="formfield" name="product_id" value="#pair#">
<cfhttpparam type="formfield" name="side" value="#side#">
<cfhttpparam type="formfield" name="type" value="#type#">
<cfhttpparam type="formfield" name="size" value="#size#">
<cfhttpparam type="header" name="CB-ACCESS-KEY" value="#cb_key#">
<cfhttpparam type="header" name="CB-ACCESS-PASSPHRASE" value="#cb_pass#">
<cfhttpparam type="header" name="CB-ACCESS-SIGN" value="#cas#">
<cfhttpparam type="header" name="CB-ACCESS-TIMESTAMP" value="#unixstamp#">
</cfhttp>
This is from COINBASE API Documentation
Signing a Message
The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp + method + requestPath + body (where + represents string concatenation) and base64-encode the output. The timestamp value is the same as the CB-ACCESS-TIMESTAMP header.
The body is the request body string or omitted if there is no request body (typically for GET requests).
The method should be UPPER CASE.
Remember to first base64-decode the alphanumeric secret string (resulting in 64 bytes) before using it as the key for HMAC. Also, base64-encode the digest output before sending in the header.
var crypto = require('crypto');
var cb_access_timestamp = Date.now() / 1000; // in ms
var cb_access_passphrase = '...';
var secret = 'PYPd1Hv4J6/7x...';
var requestPath = '/orders';
var body = JSON.stringify({
price: '1.0',
size: '1.0',
side: 'buy',
product_id: 'BTC-USD'
});
var method = 'POST';
// create the prehash string by concatenating required parts
var message = cb_access_timestamp + method + requestPath + body;
// decode the base64 secret
var key = Buffer(secret, 'base64');
// create a sha256 hmac with the secret
var hmac = crypto.createHmac('sha256', key);
// sign the require message with the hmac
// and finally base64 encode the result
var cb_access_sign = hmac.update(message).digest('base64');
I haven't used that API and unfortunately their documentation is sorely lacking any concrete examples of a generated signature. However, a good starting point would be to run the NodeJS example and output the results. Then attempt to match them with CF.
If you're not running NodeJS locally, use an online tool like JDoodle. For testing purposes, use a static timestamp and a valid base54 string for the secret. Output the generated values at the end of the script.
var crypto = require('crypto');
var cb_access_timestamp = 1645506818.784;
var secret = 'c2VjcmV0IHZhbHVl'; //"secret value" in plain text
// ... rest of code ...
console.log("cb_access_timestamp=", cb_access_timestamp);
console.log("secret=", secret);
console.log("requestPath=", requestPath);
console.log("body=", body);
console.log("method=", method);
console.log("body=", body);
console.log("cb_access_sign="+ cb_access_sign);
Next try and replicate those results in CF. Starting with the same static values
cb_access_timestamp = 1645506818.784;
secret = "c2VjcmV0IHZhbHVl"; // "secret value" in plain text
requestPath = '/orders';
method = 'POST';
Take special care when constructing json strings. Always assume API's are case sensitive, unlike CF. Your current code produces a json string with all upper case key names, (unlike the NodeJS example). To preserve the correct case, enclose the key names in quotes.
// use "[" and "]" to create ordered structure
bodyStruct = [
"price" : "1.0",
"size" : "1.0",
"side" : "buy",
"product_id" : "BTC-USD"
];
Also note that CF will be overly helpful and try and guess the data types of the structure values when serializing. You may need to use setMetaData() to force keys like price and size to be treated as strings, instead of numbers:
bodyStruct.setMetaData({
"price": {type : "string"}
, "size" : {type : "string"}
});
body = serializeJSON( bodyStruct );
Finally, don't get tripped up by encodings when converting the hmac value into base64. The current code passes the HMAC() result into ToBase64(). That produces the wrong result because ToBase64() can only convert plain strings (ascii, utf8, etc..) and HMAC() returns a hexadecimal encoded string. Instead use BinaryDecode() and BinaryEncode() to convert the hmac from hex to base64.
message = cb_access_timestamp & method & requestPath & body;
key = secret.binaryDecode("base64");
hmacHex = hmac( message, key, "HMACSHA256");
cb_access_sign = binaryEncode(hmacHex.binaryDecode("hex"), "base64");
If you output the CF results, they should match those from the NodeJS example.
// Results
writeDump({
"cb_access_timestamp": cb_access_timestamp
, "secret" : secret
, "requestPath" : requestPath
, "body" : body
, "method" : method
, "body" : body
, "cb_access_sign" : cb_access_sign
});
NodeJS Results:
cb_access_timestamp = 1645506818.784
secret = c2VjcmV0IHZhbHVl
requestPath = /orders
body = {"price":"1.0","size":"1.0","side":"buy","product_id":"BTC-USD"}
method = POST
body = {"price":"1.0","size":"1.0","side":"buy","product_id":"BTC-USD"}
cb_access_sign = neoPZ9ZKzQKoPGCmOuUVRY9v1NOpADQlOF+TZi2W8Qc=
CF Results:
Variable
Value
body
{"price":"1.0","size":"1.0","side":"buy","product_id":"BTC-USD"}
cb_access_sign
neoPZ9ZKzQKoPGCmOuUVRY9v1NOpADQlOF+TZi2W8Qc=
cb_access_timestamp
1645506818.784
method
POST
requestPath
/orders
secret
c2VjcmV0IHZhbHVl
If the results match, update the timestamp and secret and try passing the request parameters (size, type, product_id, side) in the request body, not separately as type="formfield"
cfhttp(url="#base_api#", method="post", result="result", charset="utf-8") {
cfhttpparam( type="header", name="CB-ACCESS-KEY", value="#cb_access_key#");
cfhttpparam( type="header", name="CB-ACCESS-PASSPHRASE", value="#cb_access_passphrase#");
cfhttpparam( type="header", name="CB-ACCESS-SIGN", value="#cb_access_sign#");
cfhttpparam( type="header", name="CB-ACCESS-TIMESTAMP", value="#cb_access_timestamp#");
cfhttpparam( type="header", name="Content-Type", value="application/json");
cfhttpparam( type="body", value="#body#");
}
I have an array "varray" which needs to be split into four structures. The first of each four elements should be in structure 1, the second in structure 2, etc. I have some working code to do this, but it feels to me like there should be a less cumbersome way. Here is the code:
<cfset xord = StructNew()>
<cfset xsort = StructNew()>
<cfset xsel = StructNew()>
<cfset xmer = StructNew()>
<cfloop from = '1' to = "#ArrayLen(varray)#" index = 'i'>
<cfset fieldname = farray[i]> <!---farray previously defined --->
<cfset val = varray[i]> <!---varray previously defined --->
<cfset j = i%4>
<cfif j EQ 1>
<cfset xord[fieldname] = val>
<cfselseif j EQ 2>
<cfset xsort[fieldname]= val>
<cfelseif j EQ 3>
<cfset xsel[fieldname] = val>
<cfelseif j EQ 0>
<cfset xmer[fieldname] = val>
</cfif>
</cfloop>
Can anyone suggest a better way to do this?
It's been ages i did some CF, but a tag based approach by making use of local scope:
<cfset keys = ['xord', 'xsort', 'xsel', 'xmer'] />
<cfset farray = ['f1','f2','f3','f4','f5','f6','f7','f8']>
<cfset varray = ['v1','v2','v3','v4','v5','v6','v7','v8']>
<cfloop from="1" to="#ArrayLen(varray)#" index="i">
<cfset local[keys[i%4+1]][farray[i]] = varray[i]>
</cfloop>
<cfdump var="#xord#" />
<cfdump var="#xsort#" />
<cfdump var="#xsel#" />
<cfdump var="#xmer#" />
Now you have xord, xsort, xsel and xmer filled with the right key-value pairs within your local scope.
How about cfscript?
<cfscript>
function groupByOp(values, fieldnames) {
var ops = ['mer', 'ord', 'sort', 'sel'];
var byOp = {};
arrayEach(values, function (val, i) {
byOp["x#ops[i % 4 + 1]#"][fieldnames[i]] = val;
});
return byOp;
}
</cfscript>
This makes use of the fact that CF will automagically create structs when you mention a non-existing member.
Test:
<cfset v = ListToArray('1,2,3,4,5,6')>
<cfset f = ListToArray('a,b,c,d,e,f')>
<cfoutput>
<pre>#SerializeJSON(groupByOp(v, f))#</pre>
</cfoutput>
outputs
{
"xsel": {
"c": "3"
},
"xord": {
"e": "5",
"a": "1"
},
"xsort": {
"b": "2",
"f": "6"
},
"xmer": {
"d": "4"
}
}
(You didn't mention your version, so I don't know if you have access to newer functions like array each(). Keep in mind there's slicker options in newer versions)
Instead of creating separate variables, create a single structure containing the 4 variables, and an array of names. Then use the array and MOD to populate the substructures. Note, the example below creates the subsubstructures up front to ensure they always exist - even if the field/value arrays are empty or contain less than 4 elements.
TryCF.com Example
<cfset farray = ["A","B","C","D","E","F","G","Q","T"]>
<cfset varray = ["11","22","33","RR","55","NN","77","68","46"]>
<cfset data = {xOrd={},xSort={},xSel={},xMer={}}>
<cfset names = ["xOrd","xSort","xSel","xMer"]>
<cfloop from="1" to="#ArrayLen(varray)#" index="i">
<cfset fieldName = farray[i]>
<cfset fieldValue = varray[i]>
<cfset structName = names[i%4+1]>
<cfset data[structName][fieldName] = fieldValue>
</cfloop>
The substructures can be accessed through the parent, data.
<cfdump var="#data.xOrd#" label="data.xOrd">
<cfdump var="#data.xSort#" label="data.xSort">
<cfdump var="#data.xSel#" label="data.xSel">
<cfdump var="#data.xMer#" label="data.xMer">
Another couple of approaches.
I wouldn't necessarily say that these are better, but just different:
Requires field array length to match value array length
<cfset farray = ['field1','field2','field3','field4','field5']>
<cfset varray = ['apple','orange','pear','kiwi','pineapple']>
<cfset xord = {}>
<cfset xsort = {}>
<cfset xsel = {}>
<cfset xmer = {}>
<cfloop from="1" to="#ArrayLen(varray)#" index="i">
<cfset j =
((i%4 EQ 1) ? (StructInsert(xord,farray[i],varray[i])):
((i%4 EQ 2) ? (StructInsert(xsort,farray[i],varray[i])):
((i%4 EQ 3) ? (StructInsert(xsel,farray[i],varray[i])):
((i%4 EQ 0) ? (StructInsert(xmer,farray[i],varray[i])): 0))))>
</cfloop>
<cfdump var="#xord#" />
<cfdump var="#xsort#" />
<cfdump var="#xsel#" />
<cfdump var="#xmer#" />
OR:
<cfloop from="1" to="#ArrayLen(varray)#" index="i">
<cfswitch expression="#i%4#">
<cfcase value="0">
<cfset xmer[farray[i]] = varray[i]>
</cfcase>
<cfcase value="1">
<cfset xord[farray[i]] = varray[i]>
</cfcase>
<cfcase value="2">
<cfset xsort[farray[i]] = varray[i]>
</cfcase>
<cfcase value="3">
<cfset xsel[farray[i]] = varray[i]>
</cfcase>
</cfswitch>
</cfloop>
<cfdump var="#xord#" />
<cfdump var="#xsort#" />
<cfdump var="#xsel#" />
<cfdump var="#xmer#" />
Field array length does not have to match value array length
<cfset farray = ['field1','field2','field3','field4']>
<cfset varray = ['apple','orange','pear','kiwi','pineapple']>
<cfset xord = {}>
<cfset xsort = {}>
<cfset xsel = {}>
<cfset xmer = {}>
<cfloop from="1" to="#ArrayLen(varray)#" index="i">
<cfset j =
((i%4 EQ 1 AND ArrayIsDefined(farray,i)) ? (StructInsert(xord,farray[i],varray[i],true)) :
((i%4 EQ 2 AND ArrayIsDefined(farray,i)) ? (StructInsert(xsort,farray[i],varray[i],true)) :
((i%4 EQ 3 AND ArrayIsDefined(farray,i)) ? (StructInsert(xsel,farray[i],varray[i],true)) :
((i%4 EQ 0 AND ArrayIsDefined(farray,i)) ? (StructInsert(xmer,farray[i],varray[i],true)) : 0))))>
</cfloop>
<cfdump var="#xord#" />
<cfdump var="#xsort#" />
<cfdump var="#xsel#" />
<cfdump var="#xmer#" />
OR:
<cfloop from="1" to="#ArrayLen(varray)#" index="i">
<cfswitch expression="#i%4#">
<cfcase value="0">
<cfset (ArrayIsDefined(farray,i) ? (StructInsert(xmer,farray[i],varray[i],true)) : 0)>
</cfcase>
<cfcase value="1">
<cfset (ArrayIsDefined(farray,i) ? (StructInsert(xord,farray[i],varray[i],true)) : 0)>
</cfcase>
<cfcase value="2">
<cfset (ArrayIsDefined(farray,i) ? (StructInsert(xsort,farray[i],varray[i],true)) : 0)>
</cfcase>
<cfcase value="3">
<cfset (ArrayIsDefined(farray,i) ? (StructInsert(xsel,farray[i],varray[i],true)) : 0)>
</cfcase>
</cfswitch>
</cfloop>
<cfdump var="#xord#" />
<cfdump var="#xsort#" />
<cfdump var="#xsel#" />
<cfdump var="#xmer#" />
I'm trying to write a savecontent in cfscript.
I have it in CFML:
<cfsavecontent variable="errortext">
<cfoutput>
An error occurred: http://#cgi.server_name##cgi.script_name#?#cgi.query_string#<br />
Time: #dateFormat(now(), "short")# #timeFormat(now(), "short")#<br />
<cfdump var="#session#" label="Session">
<cfdump var="#error#" label="Error">
<cfdump var="#form#" label="Form">
<cfdump var="#url#" label="URL">
</cfoutput>
</cfsavecontent>
I need it in cfscript, but how can I do it? Can I put everything inside WriteOuput()?
Thanks
Example not working:
<cfscript>
local.test = "TEST";
local.OK = "OK";
savecontent variable="contentViaScript" {
writeOutput(
"String Message: <br />"
writeDump(local);
);
}
writeOutput(contentViaScript);
</cfscript>
<cfdump var = '#contentViaScript#' />
You don't need to wrap writedump in writeoutput.
<cfscript>
local.test = "TEST";
local.OK = "OK";
savecontent variable="contentViaScript" {
writeOutput("String Message: <br />");
writeDump(local);
}
writeOutput(contentViaScript);
</cfscript>
<cfdump var = '#contentViaScript#' />
DEMO
Try this:
<cfscript>
local.test = "TEST";
local.OK = "OK";
savecontent variable="contentViaScript" {
writeOutput(
"String Message: <br />
#writeDump(local)#"
);
}
writeOutput(contentViaScript);
</cfscript>
<cfdump var = '#contentViaScript#' />
I have this function to generate slugs in Coldfusion:
<cffunction name="generateSlug" output="false" returnType="string">
<cfargument name="str">
<cfargument name="spacer" default="-">
<cfset var ret = "" />
<cfset str = lCase(trim(str)) />
<cfset str = reReplace(str, "[àáâãäå]", "a", "all") />
<cfset str = reReplace(str, "[èéêë]", "e", "all") />
<cfset str = reReplace(str, "[ìíîï]", "i", "all") />
<cfset str = reReplace(str, "[òóôö]", "o", "all") />
<cfset str = reReplace(str, "[ùúûü]", "u", "all") />
<cfset str = reReplace(str, "[ñ]", "n", "all") />
<cfset str = reReplace(str, "[^a-z0-9-]", "#spacer#", "all") />
<cfset ret = reReplace(str, "#spacer#+", "#spacer#", "all") />
<cfif left(ret, 1) eq "#spacer#">
<cfset ret = right(ret, len(ret)-1) />
</cfif>
<cfif right(ret, 1) eq "#spacer#">
<cfset ret = left(ret, len(ret)-1) />
</cfif>
<cfreturn ret />
</cffunction>
and then i am calling it using this:
<cfset stringToBeSlugged = "This is a string abcde àáâãäå èéêë ìíîï òóôö ùúûü ñ año ñññññññññññññ" />
<cfset slug = generateSlug(stringToBeSlugged) />
<cfoutput>#slug#</cfoutput>
But this is output me this slug:
this-is-a-string-abcde-a-a-a-a-a-a-e-e-e-e-i-i-i-i-o-o-o-o-u-u-u-u-n-a-no-n-n-n-n-n-n-n-n-n-n-n-n-n
it seems that all the accented characters are correctly replaced but this function is inserting a '-' after replacing them. Why?
Where is the error?
PD: i am expecting this output:
this-is-a-string-abcde-aaaaaa-eeee-iiii-oooo-uuuu-n-ano-nnnnnnnnnnnnn
Thanks.
Does this work for you? (I've adapted a similar script that we use internally.) I believe that we used this with ColdFusion 8 as we are still use it w/CF9.
<cffunction name="generateSlug" output="false" returnType="string">
<cfargument name="str" default="">
<cfargument name="spacer" default="-">
<cfset var ret = replace(arguments.str,"'", "", "all")>
<cfset ret = trim(ReReplaceNoCase(ret, "<[^>]*>", "", "ALL"))>
<cfset ret = ReplaceList(ret, "À,Á,Â,Ã,Ä,Å,Æ,È,É,Ê,Ë,Ì,Í,Î,Ï,Ð,Ñ,Ò,Ó,Ô,Õ,Ö,Ø,Ù,Ú,Û,Ü,Ý,à,á,â,ã,ä,å,æ,è,é,ê,ë,ì,í,î,ï,ñ,ò,ó,ô,õ,ö,ø,ù,ú,û,ü,ý, ,&", "A,A,A,A,A,A,AE,E,E,E,E,I,I,I,I,D,N,O,O,O,O,O,0,U,U,U,U,Y,a,a,a,a,a,a,ae,e,e,e,e,i,i,i,i,n,o,o,o,o,o,0,u,u,u,u,y, , ")>
<cfset ret = trim(rereplace(ret, "[[:punct:]]"," ","all"))>
<cfset ret = rereplace(ret, "[[:space:]]+","!","all")>
<cfset ret = ReReplace(ret, "[^a-zA-Z0-9!]", "", "ALL")>
<cfset ret = trim(rereplace(ret, "!+", arguments.Spacer, "all"))>
<cfreturn ret>
</cffunction>
<cfset stringToBeSlugged = "This is a string abcde àáâãäå èéêë ìíîï òóôö ùúûü ñ año ñññññññññññññ" />
<cfoutput>"#stringToBeSlugged# = #generateSlug(stringToBeSlugged)#</cfoutput>
Support for more International Character
If you want to widen your support for international characters, you could use ICU4J (java) and Paul Hastings' Transliterator.CFC to transliterate all of the characters and then replace any remaining spaces, dashes, slashes, etc with dashes.
https://gist.github.com/JamoCA/ec4617b066fc4bb601f620bc93bacb57
http://site.icu-project.org/download
After installing both, you can convert non-Latin characters by identifying the language id (to be converted to) and pass the string to be converted:
<cfset Transliterator = CreateObject("component","transliterator")>
<cfoutput>
<cfloop array="#TestStrings#" index="TestString">
<h3>TestString = "#TestString#"</h3>
<blockquote>
<div>CFC-1 = #Transliterator.transliterate('Latin-ASCII', TestString)#</div>
<div>CFC-2 = #Transliterator.transliterate('any-NFD; [:nonspacing mark:] any-remove; any-NFC', TestString)#</div>
</blockquote>
<hr>
</cfloop>
</cfoutput>
<h2>Available Language IDs</h2>
<cfdump var="#Transliterator.getAvailableIDs()#" label="Language IDs">
My JSON contains special characters like: new line, double quotes etc.
I am creating the JSON using Coldfusion server side script. But in case of special chars I get error due to wrongly formatted JSON. What should I do in such a case?
<cfoutput>
[
<cfset loopIndex=0>
<cfloop query="qEvents">
<cfif loopIndex NEQ 0>,</cfif>
<cfif is_allday EQ 1>
<cfset isallDayEvent = "true">
<cfelse>
<cfset isallDayEvent = "false">
</cfif>
{
"title": "#title#",
"start": "#DateFormat(start_date_time,'mm/dd/yyyy')# #TimeFormat(start_date_time,'hh:mm tt')#",
"end": "#DateFormat(end_date_time,'mm/dd/yyyy')# #TimeFormat(end_date_time,'hh:mm tt')#",
"allDay": #isallDayEvent#,
"eventID": "#event_id#",
"duration": "#duration#",
"note": "#note#",
"location": "#location#"
}
<cfset loopIndex=loopIndex+1>
</cfloop>
]
</cfoutput>
Rather than writing the JSON by hand, you should generate an array of structs and then use serializeJSON() to convert it to a valid JSON string:
<cfset thisArrayBecomesJSON = [] />
<cfloop query="qEvents">
<cfif is_allday EQ 1>
<cfset isAllDayEvent = "true" />
<cfelse>
<cfset isAllDayEvent = "false" />
</cfif>
<cfset thisEvent = {
'title' = title,
'start' = dateFormat( start_date_time, 'mm/dd/yyyy' ) & timeFormat( start_date_time, 'hh:mm tt' ),
'end' = dateFormat( end_date_time, 'mm/dd/yyyy' ) & timeFormat( end_date_time, 'hh:mm tt' ),
'allDay' = isAllDayEvent,
'eventID' = event_id,
'duration' = duration,
'note' = note,
'location' = location
} />
<cfset arrayAppend( thisArrayBecomesJSON, thisEvent ) />
</cfloop>
<cfset myJSON = serializeJSON( thisArrayBecomesJSON ) />
<cfoutput>#myJSON#</cfoutput>
This is untested, but I think it should work ok - there may be some syntax errors.