SHA-512 hashing a byte array in ColdFusion - coldfusion

I am using ColdFusion 9
Referencing Ben Nadel's good works on his blog, I tried
ucase(digestUtils.sha512(imageBinary))
For SHA-512 hashing I get that dreaded:
The sha512 method was not found. Either there are no methods with the
specified method name and argument types or the sha512 method is
overloaded with argument types that ColdFusion cannot decipher
reliably. ColdFusion found 0 methods that match the provided
arguments. If this is a Java object and you verified that the method
exists, use the javacast function to reduce ambiguity.
Now I know that sha512 does indeed exist as a method, because I saw it here, but when I perform a
cfdump var="#digestUtils#"
I only get:
md5(byte[]) byte[]
md5(java.lang.String) byte[]
md5Hex(byte[]) java.lang.String
md5Hex(java.lang.String) java.lang.String
sha(java.lang.String) byte[]
sha(byte[]) byte[]
shaHex(java.lang.String) java.lang.String
shaHex(byte[]) java.lang.String
What happened to the other methods? I guess I have to try something else.
Please advise with a ColdFusion solution. A ColdFusion/Java solution would be ok too.
I'm trying to write a SSO application where the 3rd party guys feeds me URL parameters. I have successfully decoded the 1st parameter to get my XML Post. I now need to take the 2nd parameter which is the hash payload and go through the algorithm to ensure my 1st parameter hasn't been tampered with.
=========
Editing begins here: Okay,I tried writing the code again to no avail.
The algorithm sounds simple enough. But trying to implement it is killing me.
1. compute the hash string value of the XMLPost string above:
a. convert the base64 salt string to a UTF-8 byte array.
b. convert the base64 XML payload string to a UTF-8 byte array.
c. create a new byte array consisting of the XML payload bytes from step b, appended with the salt bytes from step a.
d. perform a SHA512 hash on the concatenated byte array from step c, which results in a hashed byte array.
e. create a new byte array consisting of the hashed bytes from step d, appended with the salt bytes from step a.
f. convert the result of step e to a base64-encoded string and should be the value of query string parameter "h" payload hash.
xmlPost was created by my third party guys as such:
This XML payload string was converted to a UTF-8 byte array, which was then converted to a base-64 string. The resulting base-64 string is the value of my xmlPost below.
So I do this:
<code>
<cfset xmlPost = urlDecode("PD94bWwgdmVyc2lvbj0iMS4wIj8%2bPEVzdG9yZVNzb0N1c3RvbWVyIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzZD0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiPjxDdXN0b21lcklkPjExMjk0MDwvQ3VzdG9tZXJJZD48RGVhbGVyQ29kZT5OODg4ODg8L0RlYWxlckNvZGU%2bPFBvaW50QmFsYW5jZT4yODA8L1BvaW50QmFsYW5jZT48Rmlyc3ROYW1lPkZhaXRoPC9GaXJzdE5hbWU%2bPExhc3ROYW1lPkh1dHVsYTwvTGFzdE5hbWU%2bPC9Fc3RvcmVTc29DdXN0b21lcj4%3d") />
<cfset salt = "3dfjh674!MujErf98344#090" />
<cfset payload_hash = urlDecode("EtLDRJfcRESFKpY4OGZZnRSN2THqT%2bEelzOuXVU06jotd2kE4yKnlYay7BqyAdcUSATRgSMaHxZa6uBqKKd9rjNkZmpoNjc0IU11akVyZjk4MzQ0QDA5MA%3d%3d") />
<cfset strXML = ToString( ToBinary( xmlpost ) ) /> <!--- to get actual XML --->
<!--- base64 encoding returns a byte array --->
<cfset saltByteArray = toBase64( salt, "utf-8" ) />
<cfset xmlpostByteArray = toBase64( xmlPost, "utf-8" ) />
<!--- append salt to xmlpost --->
<cfset xmlpostsaltByteArray = xmlpostByteArray & saltByteArray />
<!--- now let us perform a sha512 hash on this concatenated byte array --->
<cfscript>
// Create an instance of our DigestUtils class
digestUtils = createObject("java","org.apache.commons.codec.digest.DigestUtils");
// I hash a byte array using the given algorithm and return a
// 32-character Hexadecimal string. Home-made hash function for CF9 and earlier
function hashBytes( bytes, algorithm = "SHA-512" ){
// Get our instance of the digest algorithm that we'll use
// to hash the byte array.
var messageDigest = createObject( "java", "java.security.MessageDigest" ).getInstance( javaCast( "string", algorithm ) );
// Get the digest for the given byte array. This returns the
// digest (i.e., hash) in byte-array format.
var digest = messageDigest.digest( bytes );
// Now that we have our digested byte array (i.e., our hash as another byte
// array), we have to convert that into a HEX string. So, we'll need a HEX buffer.
var hexBuffer = [];
// Each integer in the byte digest needs to be converted into
// a HEX character (with possible leading zero).
for (byte =1 ;byte LTE ArrayLen(digest);byte = byte + 1) {
//for ( var byte in digest){
// Get the hex value for this byte. When converting the
// byte, only use the right-most 8 bits (last 8 bits of the integer)
// otherwise the sign of the byte can create oddities
var tail = bitAnd( 255, byte );
// Get the hex-encoding of the byte.
var hex = ucase( formatBaseN( tail, 16 ) );
// In order to make sure that all of the HEX characters
// are two-digits, we have to prepend a zero for any
// value that was originally LTE to 16 (the largest value
// that won't result in two HEX characters).
arrayAppend( hexBuffer, (tail <= 16 ? ("0" & hex) : hex) );
}
// Return the flattened character buffer.
return( arrayToList( hexBuffer, "" ) );
}
// Get the hash of the byte array using our hashBytes() function
hashByteArray = hashBytes( xmlpostsaltByteArray );
</cfscript>
<!--- The hashByteArray is in HEX format now. Convert to binary --->
<!--- You must binary decode the hashed string before converting it to binary --->
<cfset hashByteArray = toBase64( BinaryDecode( hashByteArray, 'HEX' ) ) />
<!--- The final step is to append this new hashbytearray with the salt byte array --->
<cfset hashByteArray = hashByteArray & saltByteArray />
<!--- now convert this value to a base64 encoded string --->
<cfset hashByteArray2 = toBase64( hashByteArray )/>
Here is what I get for my strXML variable:
Actual xml structure converted from base 64 to string:
<?xml version="1.0"?><EstoreSsoCustomer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><CustomerId>112940</CustomerId><DealerCode>N88888</DealerCode><PointBalance>280</PointBalance><FirstName>Faith</FirstName><LastName>Hutula</LastName></EstoreSsoCustomer>
The final value, hasByteArray2 is not even remotely similar to payload_hash
This is my first time doing this and my understanding of hashing, byte arrays and character conversions flew out of the window decades ago.
What am I doing wrong?
Thank you
Faith Sloan

DigestUtils.sha512 was added in version 1.4. ColdFusion 9 uses an older version, 1.3. That is why the method is not found.
Use the other function based on MessageDigest. Just be sure to pass in the correct algorithm ie:
imageHash = hashBytes( imageBinary, "SHA-512" );
UPDATE: Based on the updated code, some of the instructions may be a bit misleading. I believe they just mean decode the xml and salt strings from their given encoding (base64 and utf-8) into byte arrays, not strings:
// note: salt value has invalid characters for base64
// assuming it is a plain utf-8 string
saltArray = charsetDecode(salt, "utf-8");
xmlByteArray = binaryDecode(xmlPost, "base64");
Then merge the two binary arrays (see custom function)
mergedBytes = mergeArrays( xmlByteArray, saltArray );
Calculate the hash of the new byte array:
messageDigest = createObject( "java", "java.security.MessageDigest" );
messageDigest = messageDigest.getInstance( javaCast( "string", "SHA-512") );
hashedByteArray = messageDigest.digest( javacast("byte[]", mergedBytes) );
Merge the arrays again:
mergedBytes = mergeArrays( hashedByteArray, saltArray);
Finally convert the binary to base64 and compare:
calculatedPayload = binaryEncode( javacast("byte[]", mergedBytes), "base64");
// check results
arePayloadsEqual = compare(calculatedPayload, payload_hash) eq 0;
WriteDump("arePayloadsEqual="& arePayloadsEqual);
WriteDump("calculatedPayload="& calculatedPayload);
WriteDump("payload_hash="& payload_hash);
Note: BinaryDecode/CharsetDecode return java arrays. Unlike CF arrays, they are immutable (ie cannot be changed). So the handy addAll(..) trick will not work here.
// merge immutable arrays the long way
function mergeArrays( array1, array2 ){
var i = 0;
var newArray = [];
for (i = 1; i <= arrayLen(arguments.array1); i++) {
arrayAppend(newArray, arguments.array1[i]);
}
for (i = 1; i <= arrayLen(arguments.array2); i++) {
arrayAppend(newArray, arguments.array2[i]);
}
return newArray;
}

Related

c++ md5 hash not equal to vb.net code socket

server -> AUTH GateKeeper S :GKSSP\0\0\0\0\0\0\0\0\0r!W\tvM
Client -> AUTH GateKeeper S :GKSSP\0\0\0\0\0\0\0\0\0"g#çójDî3(ƒP¡Á"
VB.NET tcp code:
Do While Stream.CanRead
responseData = Trim(System.Text.Encoding.UTF8.GetString(data2, 0, bytes))
Feed = responseData.Split(Chr(13), Chr(10))
Dim datax As [Byte]()
For Each line As String In Feed
If line <> "" Then
Select Case UCase(Split(line.Trim, " ")(0))
Case "AUTH"
If (Split(line, " ")(2) = "S") And (Split(line, " ")(3) <> ":OK") Then
ChOK = ":GKSSP\0\0\0" & Chr(2) & "\0\0\0" & Chr(3) & "\0\0\0" & Challenge_1(Mid(line, InStr(line, "\0\0\0\0\0\0") + Len("\0\0\0\0\0\0"))) & "Sm(" &
HexToAsc("e4") & "HS" & HexToAsc("c1") & "M" & HexToAsc("847f8293f98d") & "UC"
message = "AUTH GateKeeper S " & ChOK & vbCrLf
VB.NET challenge functions
Public Function Challenge_1(Challenge As String) As String
Dim c1 As String, a1 As String, c2 As String, a2 As String
c1 = "edp{}e|wxrdse}}u666666666666666666666666666666666666666666666666" + Challenge
a1 = HexToAsc(GetIRC7PWD(c1))
Dim Aaah As New String("\"c, 48)
c2 = HexToAsc("0f0e1a11170f161d12180e190f17171f") & Aaah & a1
a2 = HexToAsc(GetIRC7PWD(c2))
Challenge_1 = a2
End Function
Public Function GetIRC7PWD(source As String)
Dim md5Hash As MD5 = MD5.Create()
' Dim source As String = "edp{}e|wxrdse}}u666666666666666666666666666666666666666666666666"
Dim data As Byte() = md5Hash.ComputeHash(Encoding.Default.GetBytes(source))
Dim sBuilder As New StringBuilder()
Dim i As Integer
For i = 0 To data.Length - 1
sBuilder.Append(data(i).ToString("x2"))
Next i
Return sBuilder.ToString()
End Function
The above code authenticates successfully.
My C++ code works fine, until it reads data sent from the AUTH and MD5's it. However, it creates the wrong MD5 hash:
while (true)
{
ZeroMemory(buff, 1024);
int bytesReceived = NetDll_recv(XNCALLER_SYSAPP,Sock, buff, 1024, 0);
if (bytesReceived == SOCKET_ERROR)
{
}
if (bytesReceived > 0) {
std::string Recieved(buff, bytesReceived);
std::stringstream iss(Recieved);
while(iss.good())
{
std::string SingleLine;
getline(iss,SingleLine);
if (!SingleLine.empty()) {
stringstream ss(SingleLine);
string s;
int xUp = 1;
vector <string> tokens;
while (getline(ss, s, ' ')) {
tokens.push_back(s);
xUp++;
}
if (tokens[0] == "AUTH" && tokens[2] == "S") {
MD5 md5;
string ChOK3 = SingleLine.substr(46);
string ChallengeCode = "edp{}e|wxrdse}}u666666666666666666666666666666666666666666666666" + ChOK3;
char* cz = const_cast<char*>(ChallengeCode.c_str());
string g1 = md5.digestString( cz );
string a1 = hexToASCII(g1);
int n = 48;
char cx = '\\';
string c2 = hexToASCII("0f0e1a11170f161d12180e190f17171f") + std::string(n, cx) + a1;
char* c = const_cast<char*>(c2.c_str());
string a2 = md5.digestString( c );
string a3 = hexToASCII(a2);
//string ChOK4 = ChOK + a2 + "Sm(" + hexToASCII("e4") + "HS" + hexToASCII("c1") + "M" + hexToASCII("847f8293f98d") + "UC\r\n";
string ChOK4 = "AUTH GateKeeper S :GKSSP\\0\\0\\0\x02\\0\\0\\0\x03\\0\\0\\0" + a3 + "Sm(" + hexToASCII("e4") + "HS" + hexToASCII("c1") + "M" + hexToASCII("847f8293f98d") + "UC\r\n";
const char *cstr = ChOK4.c_str();
NetDll_send(XNCALLER_SYSAPP, Sock,ChOK4.c_str(),ChOK4.length(), 0);
}
It looks like my code is the same.
Is it because the characters are UTF-16? How can I read the buffer in UTF-16 so that I can properly MD5 the string?
The two codes are NOT doing the same thing, which is why they are producing different results. Differences are highlighted below:
While reading from the socket:
the VB code is:
reading arbitrary bytes (without regard to message boundaries)
converting the bytes from UTF-8 to a UTF-16 string (without regard to codeunit boundaries)
trimming the string
splitting the string on line breaks
parsing each line
the C++ code is:
reading arbitrary bytes (without regard to message boundaries)
converting the bytes as-is to a std::string (without regard to encoding)
splitting the string on line breaks (without trimming it first)
parsing each line
Up to this point, given the data shown, the results are logically the same for both codes (though technically different layouts in memory), even though they are being processed differently. But even up to this point, both codes are NOT handling the socket data adequately, and there IS potential for data corruption and data loss here.
Now, assuming no corruption/loss has actually occurred, then for the actual AUTH command:
the VB code is:
extracting the challenge from position 37, after the first "\0\0\0\0\0\0" substring (thus, the extracted challenge is "\0\0\0r!W\tvM")
AUTH GateKeeper S :GKSSP\0\0\0\0\0\0\0\0\0r!W\tvM
^ ^ ^
1 25 37
converting the challenge from UTF-16 to ANSI bytes
calculating an MD5 for those bytes
calculating a 2nd MD5 based on the 1st MD5
creating a response for the 2nd MD5
the C++ code is:
extracting the challenge from fixed index 46 (position 47) (thus, the extracted challenge is "tvM")
AUTH GateKeeper S :GKSSP\0\0\0\0\0\0\0\0\0r!W\tvM
^ ^ ^ ^
0 24 36 46
getting the challenge's raw UTF-8 bytes
calculating an MD5 for those bytes
calculating a 2nd MD5 based on the 1st MD5
creating a response for the 2nd MD5
So, the biggest issues I see that you need to fix are the following:
in both languages, fix the socket reading logic to avoid data loss. Right now, you are extracting all bytes from the socket and throwing away any incomplete lines that are waiting for more bytes to arrive. You need to save all raw bytes from the socket into an intermediate buffer first, and then extract only complete lines from that buffer, leaving behind incomplete lines in the buffer so they can be finished on subsequent reads.
on the VB side, for ASCII-only data (as you have shown), the conversion from UTF-8 to UTF-16 to ANSI is loss-less, but for non-ASCII data this will be lossy. Consider converting the UTF-16 data back to UTF-8 instead of ANSI, to match the C++ code.
on the C++ side, fix the code to extract the server's challenge from the correct string position to match the VB code.

Amazon s3 Test suite REST API signature calculation

I am working on implementing the Amazon REST API in our application. The application is build with WinDev. In order too test my signature calculation i desided to try the test suite provided by amazon:
https://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html
This is how i derive the hex value of my canonical request:
sCanonicalRequestHash = :HashCanonicalRequest([
GET
/
Param1=value1&Param2=value2
host:example.amazonaws.com
x-amz-date:20150830T123600Z
host;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
])
The method HashCanoncialRequest removes all the char 10 ( this is done in order to hash the string correctly) hashes the string in to binary using windev's hashstring function. This binary function is converted to a hex value, all the whitespace is removed and changed to lower case.
//Remove char 13 ,otherwise the hash fails( Windows enter )
sResult = Replace(sResult, Charact(13), "")
//Create hash
sResult = HashString(HA_SHA_256, sResult)
//Convert hash to lower case hex
sResult = Lower(BufferToHexa(sResult, 1, 32))
//Remove spaces
sResult = Replace(sResult," ", "")
This results the following value:
816cd5b414d056048ba4f7c5386d6e0533120fb1fcfa93762cf0fc39e2cf19e0
This is the value expected by the test suite. So far so good.
Next up is the string to sign, this looks as followed:
AWS4-HMAC-SHA256
20150830T123600Z
20150830/us-east-1/service/aws4_request
816cd5b414d056048ba4f7c5386d6e0533120fb1fcfa93762cf0fc39e2cf19e0
Now it's time to calculate the signingkey.
First up some values given by the test suite:
sSecret is string = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
sDate is string = "20150830"
sRegion is string = "us-east-1"
And now the calculation:
bufDateKey is Buffer = WL.HashString(HA_HMAC_SHA_256, sDate, "AWS4" + sSecret)
bufRegionKey is Buffer = WL.HashString(HA_HMAC_SHA_256, sRegion, bufDateKey)
bufServiceKey is Buffer = WL.HashString(HA_HMAC_SHA_256, "service", bufRegionKey)
bufSigningKey is Buffer = WL.HashString(HA_HMAC_SHA_256, "aws4_request", bufServiceKey)
Amazon provides a different test in order too check your calculations here and this calculation is tested and returns the value expected.
Now for the part that doesn't do what the test suite expects. The signature calculation.
//Hashing the ss with psSigningKey as the key
bufSignature = WL.HashString(HA_HMAC_SHA_256, ss, bufSigningKey)
//Converting the hash to hex
bufSignature = BufferToHexa(bufSignature, 1, 32)
//Converting the hex value to lower case and remove any whitespace
bufSignature = Replace(Lower(bufSignature), " ", "")
ss is the string value of the string to sign as shown in the third code snipped
bufSigningKey is the binary value of the result for the for last code snipped. This is converted to hex and all the white space is removed and the string is converted to lower case. This do's not return the signature as shown by the test suite.
If hope someone can help.

trying to find the value is numeric or integer from string

With the url string below, I need to find the value of the parameter named construction.
<cfset understand = "http://www.example.com/ops.cfm?id=code&construction=148&construction=150&Building=852&Building=665&Building=348&Building=619&Building=625&Building=626&_=1426353166006&action=SUBMIT">
<cfset understand2 = "http://www.example.com/ops.cfm?id=code&construction=AVENT+Construction+Site&construction=Signore+upper+constructions&Building=852&Building=665&Building=348&Building=619&Building=625&Building=626&_=1426353166006&action=SUBMIT">
I then want to check if the value is numeric or a string. I am doing this:
isDefined('understand') and isnumeric(understand)
But it always returns "NO".
Seems like a good case for REGEX, but that's not my strength. If you are always looking for the value of the same item (construction), you could take advantage of the underlying Java and use the STRING.split() method. Then use the Coldfusion val() function to see what you get. The following solution assumes that 0 is not a valid value. If it is then you have more work to do.
<cfscript>
target=understand;
//target=understand2; //uncomment to try the second values
token="construction=";
potentialValues = target.split(token); //creates an array of values using the token as a separator
for (item in potentialValues )
{
writeoutput(val(item) & "<br />"); //val will grab the numerical value and ignore everything that follows. No number will become 0
}
</cfscript>
Try this:
constructionIsAt = find(understand, "construction");
characterAfterConstruction = mid(understand, constructionIsAt + 13, 1);
if isNumeric(characterAfterConstruction) {
code for numeric
}
else {
code for non numeric
}

Remove characters in <cfoutput> with ReplaceNoCase() ColdFusion

I need to display an output from a data record that is formatted similar to this: XXXX:12345 (Xxxxxxxxx)
However, the only data I want to output is the "12345" and with two preceding zeros, i.e. the output should look like "0012345". The "12345" in the record is example only, each record has a unique number assigned. An example record looks like this: CAST:98765 (RPOS1234-XY)
Can I use the ReplaceNoCase() to pull only that data out of the record? If so, how would I write the code to remove the unwanted characters?
You can do this in one line of code using a few functions.
str = 'CAST:98765 (RPOS1234-XY)';
projectCode = '00' & listLast( listFirst( str, ' ' ), ':' );
writeDump( projectCode );
To explain this code from the inner most function going out.
ListFirst() gets the first element in an a list based on the delimiter you specify, in this case the delimiter is ' ' - a space - yes, you can use a space as a delimiter.
ListLast() gets the last element in a list based on the delimiter you specify, in this case the delimiter is ':'
The first part simplt appends '00' to the result of the above function calls.
If I had to use reReplaceNoCase or reFindNoCase this is how I would do it.
function parseTokenUsingReFindNoCase(token) {
var local = {};
// use regex to locate position of number (see only set of parentheses in regex pattern)
local.positions = reFindNoCase("^.+:(\d+).+$", arguments.token, 1, true);
// obtain the token substring and ensure at least 7 digits with preceding 0's
local.result = numberFormat( mid(arguments.token, local.positions.pos[2], local.positions.len[2]), repeatString(0, 7));
return local.result;
}
function parseTokenUsingReReplaceNoCase(token) {
var local = {};
// use regex to strip away text before and after the token
local.result = reReplaceNoCase(arguments.token, "(^\D+|\s.+$)", "", "all");
// ensure at least 7 digits with preceding 0's
local.result = numberFormat(local.result, repeatString(0, 7));
return local.result;
}
<h1>ParseToken</h1>
<h2>Using ReFindNoCase</h2>
<cfdump var="#parseTokenUsingReFindNoCase("CAST:98765 (RPOS1234-XY)")#" /><br>
<cfdump var="#parseTokenUsingReFindNoCase("CAST:591498 (FUBAR56-XE)")#" /><br>
<cfdump var="#parseTokenUsingReFindNoCase("CAST:784 (RFP4542-LL)")#" /><br>
<h2>Using ReReplaceNoCase</h2>
<cfdump var="#parseTokenUsingReReplaceNoCase("CAST:98765 (RPOS1234-XY)")#" /><br>
<cfdump var="#parseTokenUsingReReplaceNoCase("CAST:591498 (FUBAR56-XE)")#" /><br>
<cfdump var="#parseTokenUsingReReplaceNoCase("CAST:784 (RFP4542-LL)")#" /><br>
ParseToken
Using ReFindNoCase
0098765
0591498
0000784
Using ReReplaceNoCase
0098765
0591498
0000784
It doesn't use replaceNoCase, but based on your comments this will work:
<cfset castTicket = projectCode>
<!--- strip the first 5 characters, since it is always "CAST " --->
<cfset castTicket = removechars(castTicket, 1,5)>
<!--- now return the leftmost characters, up to the space --->
<cfset castTicket = left(castTicket, find(" ", castTicket) )>
<!--- format the number so it has 7 digits (2 leading zeros in this case) --->
<cfset castTicket = NumberFormat(castTicket, 0000000)>
<cfoutput>#castTicket#</cfoutput>
Returns:
0012345

How to parse out data of a string

I have a function which gets a string from another website and if I extract it I end up with the following string
IFX TMP2134567 1433010010 WT33 PARTIAL 2014-11-26 09:43:58 IFX TEMP12345 1433010003 SW80 PARTIAL 2014-11-26 09:43:10 IFX AP RETERM 007 1418310108 MB01 CONFIRMED 2014-07-03 09:48:37
In this case it's 2 records which have 6 fields each and they are all separated by a space. how can I go and read the string and add these into an structure and array to access them.
The fields would be set up like this
IFX
TMP2134567 (this field may contain a space)
1433010010
WT33
PARTIAL
2014-11-26 09:43:58.
So if we use the " " as a separator we would get 7 since the 6th is a date time and has a space between I could also use 7 since I can put 6 and 7 back together and store date and time separately.
My question is there a way to do this with 6 or if I have to use 7 how would I do that. I tried valuelist but that does not work.
I know a couple of things in my list, 1st one is always 3 Char, 4th is always 4 char and my record ends with a date time in format YYYY-MM-DD HH:MM:SS
To make it a bit more complicated I just found that the 2nd field can have spaces like in the 3rd record which looks like this "AP RETERM 007"
Another option is to create a JSON string with your data like this, and then deserialize it.
<cfsavecontent variable="sampledata">
IFX TMP2134567 1433010010 WT33 PARTIAL 2014-11-26 09:43:58 IFX TEM P12345 1433010003 SW80 PARTIAL 2014-11-26 09:43:10 IFX AP RETERM 007 1418310108 MB01 CONFIRMED 2014-07-03 09:48:37</cfsavecontent>
<cfset asJson = ReReplaceNoCase(sampledata,"\s*(.{3}) (.*?) (\d+) (.{4}) ([^\s]*) (\d+-\d+-\d+ \d+:\d+:\d+)\s*",'["\1","\2","\3","\4","\5","\6"],',"ALL")>
<!--- Replace the last comma in the generated string with a closing bracket --->
<cfset asJson = "[" & ReReplace(asJson,",$","]","ALL")>
<cfset result_array = DeSerializeJSON(asJson)>
<cfdump var="#result_array#">
You can access the data simply with the resulting array.
So here's how I understand it
3 characters
Variable string
All digits
4 characters
I assume this value never contains a space
Date/Time
Based on assuming a "yes" to my question above, this solution works:
<cfscript>
raw = " IFX TMP2134567 1433010010 WT33 PARTIAL 2014-11-26 09:43:58 IFX TEMP12345 1433010003 SW80 PARTIAL 2014-11-26 09:43:10 IFX AP RETERM 007 1418310108 MB01 CONFIRMED 2014-07-03 09:48:37";
recordPattern = "(\S+)\s+([\w\s]+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})";
keys = ["a","b","c","d","e","f"];
records = getRecordsFromString(raw, recordPattern, keys);
writeDump(records);
function getRecordsFromString(raw, pattern, keys){
var offset = 1;
var records = [];
while (true) {
var result = getRecord(raw, recordPattern, keys, offset);
offset = result.offset;
if (!offset) break;
arrayAppend(records, result.record);
}
return records;
}
function getRecord(raw, recordPattern, keys, offset){
var match = reFind(recordPattern, raw, offset, true);
if (arrayLen(match.pos) != arrayLen(keys)+1){
return {record="", offset=0};
}
var keyIdx=1;
for (var key in keys){
record[key] = mid(raw, match.pos[++keyIdx], match.len[keyIdx]);
}
return {record=record, offset=offset+match.len[1]};
}
</cfscript>
Obviously you will need to tweak the recordPattern and keys to suit your actual needs.
And if you don't understand the regular expression usage there, do yourself a favour and read up on it. I do a series on "regular expressions in CFML" on my blog, which would be an adequate starting point.