How to convert Active Directory objectGUID to UUID in Coldfusion - coldfusion

I have a CFLDAP query returning the objectGUID. How can I convert this to a valid UUID string in a 8-4-4-4-12 pattern in ColdFusion?
I have specified returnAsBinary="ObjectGUID", but toString(getLDAP.ObjectGUID) does not return the desired result.
Update:
I tried binaryEncode():
<cfset guid = binaryencode(getLDAP.objectguid,"HEX")>
Which returns:
18E0CE3388B79C4EA4D73894AE8CD8F6
But I'm expecting this (was extracted and supplied by another process that I cant see their conversion steps).
3cee018-b788-4e9c-a4d7-3894ae8cd8f6
Hummm... Although they don't match, the last half is the same. a4d7-3894ae8cd8f6.

hummm... the last half is the same
Interesting. A link from this thread explains why. Apparently, it is more involved than just converting the binary into hex, in one shot:
First, know the sequence of the byte index that forms the Dashed String:
[3] [2] [1] [0] - [5] [4] - [7] [6] - [8] [9] - [10] [11] [12] [13] [14] [15]
Next, apply bit masking to each and every single byte value accessed in the array.
Follow by converting to the Hex representation of the value.
Just make sure that the Hex value is a double digit value, meaning instead of "A", it should be "0A"
Since CF's arrays are 1-based, just add +1 to the positions. This builds the decoded string in the proper sequence (sans dashes, which you can easily add with string functions).
// Get GUID binary
bin = yourQuery.objectGUID[rowNumber];
// Extract bytes in this sequence
order = [4,3,2,1, 6,5, 8,7, 9,10,11,12,13,14,15,16];
// Stores converted bytes
hex = [];
for (pos in order) {
// Apply mask to current byte and convert to hext
arrayAppend(hex, formatBaseN(BitAnd(bin[pos], 255), 16));
}
writeOutput("Hex string = "& arrayToList(hex, ""));

Yes... I found this in an old CF post and it worked for me:
<CFLDAP ACTION="query" NAME="getLDAP" START="DC=info,DC=sys" SCOPE="subtree" STARTROW="1" maxRows="1"
SERVER="#domainCONTROLLER#" USERNAME="#USERNAME#" PASSWORD="#PASSWORD#" PORT="389" TIMEOUT="60"
ATTRIBUTES="sAMAccountName,mail,name,givenName,middleName,sn,title,department,ObjectGUID"
FILTER="sAMAccountName=#session.username#"
returnAsBinary = "ObjectGUID">
<cfset hexguid = BinaryEncode(getLDAP.objectguid,"Hex")>
<cfset sthex = toString(hexguid)>
<cfset GuidStr = mid(sthex,7,2)>
<cfset GuidStr = GuidStr & mid(sthex,5,2)>
<cfset GuidStr = GuidStr & mid(sthex,3,2)>
<cfset GuidStr = GuidStr & mid(sthex,1,2)>
<cfset GuidStr = GuidStr & mid(sthex,11,2)>
<cfset GuidStr = GuidStr & mid(sthex,9,2)>
<cfset GuidStr = GuidStr & mid(sthex,15,2)>
<cfset GuidStr = GuidStr & mid(sthex,13,2)>
<cfset GuidStr = GuidStr & mid(sthex,17,18)>
<cfset guid = left(GuidStr,8) & "-" & mid(GuidStr,9,4) & "-" & mid(GuidStr,13,4) & "-" & mid(GuidStr,17,4) & "-" & mid(GuidStr,21,18)>
Yours is more eloquent. Thanks!

Here is a javascript implementation I did for a Pentaho Kettle "Modified Javascript" step.
It receives the objectGUID from Java as byte[16] array, and continues to use Java classes for processing.
var b = objectGUID; // JavaArray
// the byte order is: [3] [2] [1] [0] - [5] [4] - [7] [6] - [8] [9] - [10] [11] [12] [13] [14] [15]
var theByteOrder = [3,2,1,0,5,4,7,6,8,9,10,11,12,13,14,15];
var ordered = new java.io.ByteArrayOutputStream(16);
theByteOrder.forEach(function(i) {
ordered.write(b[i]);
});
var bb = java.nio.ByteBuffer.wrap(ordered.toByteArray());
bb.order(java.nio.ByteOrder.BIG_ENDIAN);
var uuid = new java.util.UUID(bb.getLong(), bb.getLong());
var GUID = uuid.toString();
it's not coldfusion dialect, yes, but hopefully it will be useful for somebody scanning the web to solve a similar task.

Related

"How to fix `remove default alphabetical ordering of SerializeJSON() `

I'm trying to add the serialized data in a request to third party API which needs a specific order of the data to be maintained, but SerializeJSON orders in alphabetical order which breaks the format required by the third party API. Could someone help me to figure it out
INPUT:
<cfset data ={
"Booking": {
"ActionCode":"DI",
"AgencyNumber":"23",
"Touroperator":"TVR",
"BookingNumber":"323",
},
"Payment": {
"__type":"paymenttype",
"PaymentProfile": {
"Value": 4,
"Manual": false
},
"PaymentType": 4,
"PaymentAction":2,
"Details": {
"IBAN": "DE02120300000000202051",
"BIC": "BYLADEM1001"
}
},
"Login":{
"UserCode": "usercode",
"Password": "password"
}
}>
When this method SerializeJSON() is used on my data:
SerializeJSON(data)
Current Output
"{"Booking":{"Touroperator":"TVR","ActionCode":"DI","BookingNumber":"323","AgencyNumber":"23"},"Login":{"UserCode":"usercode","Password":"password"},"Payment":{"PaymentProfile":{"Manual":false,"Value":4},"PaymentType":4,"PaymentAction":2,"__type":"paymenttype","Details":{"BIC":"BYLADEM1001","IBAN":"DE02120300000000202051"}}}"
Expected Output:
"{"Booking":{"ActionCode":"DI","AgencyNumber":"23","Touroperator":"TVR","BookingNumber":"323",},"Payment":{"__type":"paymenttype","PaymentProfile":{"Value":4,"Manual":false},"PaymentType":4,"PaymentAction":2,"Details":{"IBAN":"DE02120300000000202051","BIC":"BYLADEM1001"}},"Login":{"UserCode":"usercode","Password":"password"}}"
Structs in ColdFusion are unordered HashMaps, so there is no order at all. You can keep insertion order by using structNew("Ordered") (introduced with ColdFusion 2016). Unfortunately you can no longer use the literal syntax anymore, but I assume you are generating the data dynamically anyway.
<cfset data = structNew("Ordered")>
<cfset data["Booking"] = structNew("Ordered")>
<cfset data["Booking"]["ActionCode"] = "DI">
<cfset data["Booking"]["AgencyNumber"] = "TVR">
<cfset data["Booking"]["BookingNumber"] = "323">
<cfset data["Payment"] = structNew("Ordered")>
<cfset data["Payment"]["__type"] = "paymenttype">
<cfset data["Payment"]["PaymentProfile"] = structNew("Ordered")>
<cfset data["Payment"]["PaymentProfile"]["Value"] = 4>
<cfset data["Payment"]["PaymentProfile"]["Manual"] = false>
etc.
If you are stuck on an older ColdFusion version, you will have to use Java's LinkedHashMap.
<cfset data = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Booking"] = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Booking"]["ActionCode"] = "DI">
<cfset data["Booking"]["AgencyNumber"] = "TVR">
<cfset data["Booking"]["BookingNumber"] = "323">
<cfset data["Payment"] = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Payment"]["__type"] = "paymenttype">
<cfset data["Payment"]["PaymentProfile"] = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Payment"]["PaymentProfile"]["Value"] = 4>
<cfset data["Payment"]["PaymentProfile"]["Manual"] = false>
etc.
But be aware: LinkedHashMap is case-sensitive (and also type-sensitive: in case your keys are numbers, it does matter!).
<cfset data = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Test"] = "">
<!---
accessing data["Test"] = works
accessing data["test"] = doesn't work
accessing data.Test = doesn't work
--->
Another issue you might encounter: Due to ColdFusion's internal type casting, serializeJSON() might stringify numbers and booleans in an unintended way. Something like:
<cfset data = structNew("Ordered")>
<cfset data["myBoolean"] = true>
<cfset data["myInteger"] = 123>
could easily end up like:
{
"myBoolean": "YES",
"myInteger": 123.0
}
(Note: The above literal syntax would work perefectly fine, but if you are passing the values around as variables/arguments, casting eventually happens.)
The easiest workaround is explicitly casting the value before serializing:
<cfset data = structNew("Ordered")>
<cfset data["myBoolean"] = javaCast("boolean", true)>
<cfset data["myInteger"] = javaCast("int", 123)>

Aggregate modis list files by month

I am looking for a more efficient way of separating each year from the time series (2002-2016) by month. I've done it by hand, but it takes a lot.
mypath<-"D:/SNOWL"
myras<-list.files(path=mypath,pattern = glob2rx("*.tif$"),
full.names = TRUE, recursive = TRUE)
> myras
[1] "D:/SNOWL/MOYDSL10A1.A2002001.tif" "D:/SNOWL/MOYDSL10A1.A2002002.tif"
[3] "D:/SNOWL/MOYDSL10A1.A2002003.tif" "D:/SNOWL/MOYDSL10A1.A2002004.tif"
[5] "D:/SNOWL/MOYDSL10A1.A2002005.tif" "D:/SNOWL/MOYDSL10A1.A2002006.tif"
[7] "D:/SNOWL/MOYDSL10A1.A2002007.tif" "D:/SNOWL/MOYDSL10A1.A2002008.tif"
[9] "D:/SNOWL/MOYDSL10A1.A2002009.tif" "D:/SNOWL/MOYDSL10A1.A2002010.tif"
[11] "D:/SNOWL/MOYDSL10A1.A2002011.tif" "D:/SNOWL/MOYDSL10A1.A2002012.tif"
serie<-orgTime(myras, nDays = "asIn", begin ="2002-01-01",end = "2016-12-31", pillow = 75, pos1 = 13, pos2 = 19)
filter<-serie$inputLayerDates
> filter
[1] "2002-01-01" "2002-01-02" "2002-01-03" "2002-01-04" "2002-01-05"
[6] "2002-01-06" "2002-01-07" "2002-01-08" "2002-01-09" "2002-01-10"
[11] "2002-01-11" "2002-01-12" "2002-01-13" "2002-01-14" "2002-01-15"
[16] "2002-01-16" "2002-01-17" "2002-01-18" "2002-01-19" "2002-01-20"
[21] "2002-01-21" "2002-01-22" "2002-01-23" "2002-01-24" "2002-01-25"
[26] "2002-01-26" "2002-01-27" "2002-01-28" "2002-01-29" "2002-01-30"
[31] "2002-01-31" "2002-02-01" "2002-02-02" "2002-02-03" "2002-02-04"
[36] "2002-02-05" "2002-02-07" "2002-02-08" "2002-02-09" "2002-02-10"
[41] "2002-02-11" "2002-02-12" "2002-02-13" "2002-02-14" "2002-02-15"
EDIT:
Ok, let's try a full size example and see if it's working for you:
# Here we generate filenames as returned from `list.files`:
rm(list = ls())
myras <- sapply(1:5465, function(i) paste0('D:/SNOWL/MOYDSL10A1.A',sample(2000:2016,1),sample(c(paste0('00',1:9),paste0('0',10:99),100:365),1),'.tif'))
head(myras)
# Let's extract the timestamps
tstmps <- regmatches(myras,regexpr('[[:digit:]]{7}',myras))
head(tstmps,50)
# And now convert the timestamps to dates
dates <- as.Date(as.numeric(substr(tstmps,5,7)) - 1, origin = paste0(substr(tstmps,1,4),"-01-01"))
head(dates,10)
# Last step is to sort the files by month
#check months
print(month.name)
myras_byM = sapply(month.name,function(x) myras[months(dates) == x])
head(myras_byM$January)
head(myras_byM$February)
head(myras_byM$March)
head(myras_byM$April)
head(myras_byM$May)
head(myras_byM$June)
head(myras_byM$July)
head(myras_byM$August)
head(myras_byM$September)
head(myras_byM$October)
head(myras_byM$November)
head(myras_byM$December)
You can easily get the date from your filename, if you have a consistent naming convention.
In your case, I see the files are ordered by year and day of the year. So just strip the date from the filename, and then you can filter it by whatever you need. To do this I'm using regular expressions. In this case, I'm interested in the date and DOY string, which should always be 7 numbers. The corresponding RE is therefore [[:digit:]]{7}, which means 7 consecutive digits. regexpr finds the matches and regmatches returns them.
dts <- regmatches(myras,regexpr('[[:digit:]]{7}',myras))
Then you just use substring to extract the digits you need (this method assumes it's always 4 digits for year followed by 3 for DOY) and convert it to a date:
dts <-as.Date(as.numeric(substr(dts,5,7)) - 1, origin = paste0(substr(dts,1,4),"-01-01"))
That would give you the variable of filter you have in your example.
If you then want to sort the entire time series by month, you could use sapply or lapply with the built-in names month.name. The base function months will return you the name of the month for a given date:
myras_byMonth <- sapply(month.name,function(x) myras[months(dts) == x])
Hope I understood your question correctly and this was what you were looking for.
Best,
Val

CFML Efficiently determine if a value exists in one of multiple lists

This is my first post to SO, a resource that is incredibly valuable!
I am trying to determine if a value (state code) exists in a list of codes and if so, set a new variable that represents the division (ie. sales territory, no match = 15). The code below works, but I want to improve my skills and do this as efficiently as possible. So my question is there a "better" way to achieve the same result?
<cfset state = "LA">
<cfset division = 15>
<cfset position = 0>
<cfset aStates = ArrayNew(1)>
<cfset aStates[1] = "MA,ME,NH,RI,VT">
<cfset aStates[2] = "CT,DE,NJ,NY,DE">
<cfset aStates[3] = "DC,MD,VA,WV">
<cfset aStates[4] = "TN">
<cfset aStates[5] = "NC,SC">
<cfset aStates[6] = "GA">
<cfset aStates[7] = "FL">
<cfset aStates[8] = "AL,KY,LA,MS">
<cfset aStates[9] = "IL,WI">
<cfset aStates[10] = "CO,MN,ND,SD,WY">
<cfset aStates[11] = "IN,OH,MI">
<cfset aStates[12] = "ID,OR,UT,WA">
<cfset aStates[13] = "AZ,HI,NV">
<cfset aStates[14] = "CA">
<cfset position = 0>
<cfloop array="#aStates#" index="lStates">
<cfset position = position+1>
<cfif ListFindNoCase(lStates,variables.state) NEQ 0>
<cfset division = position>
</cfif>
</cfloop>
<cfdump var="#aStates#" label="states array">
<cfoutput>State: #state#</cfoutput>
<cfoutput>Division: #division#</cfoutput>
With the current structure, you are relying on string comparisons, so there is not too much room for improvement.
Unless there is a specific reason you must hard code the values, a more flexible approach is to store the information the database. Create tables to store the "states" and "divisions", and another table to store the relationships:
States
StateID | Code | Name
1 | ME | Maine
2 | GA | Georgia
3 | CA | California
4 | NH | New Hampshire
...
Divisions
DivisionID | Name
1 | Sales Territory
...
DivisionStates
DivisionID | StateID
1 | 1
1 | 4
6 | 2
14 | 3
...
Then use an OUTER JOIN to retrieve the selected state and division. Assuming #state# will always contain a valid entry, something along these lines (not tested):
SELECT s.Name AS StateName
, CASE WHEN d.Name IS NULL THEN 'No Match' ELSE d.Name END AS DivisionName
FROM States s
LEFT JOIN DivisionStates ds ON ds.StateID = s.StateID
LEFT JOIN Divisions d ON d.DivisionID = ds.DivisionID
WHERE s.Code = <cfqueryparam value="#state#" cfsqltype="cf_sql_varchar">
I do not know the source of the #state# variable, but I am guessing it may be passed via a form <select>. If it is currently hard coded, you could simplify things further by using querying the "states" table, and using it to populate the list. Also, consider using the ID as the list value, rather than the state "code".
I thought of an approach in this by placing the states into a Struct; with the state codes as keys.
<cfscript>
state = "LA";
division = 15;
states_divisions_map = {
MA: 1, ME: 1, NH: 1, RI: 1, VT: 1,
CT: 2, NJ: 2, NY: 2, DE: 2,
DC: 3, MD: 3, VA: 3, WV: 3,
TN: 4,
NC: 5, SC: 5,
GA: 6,
FL: 7,
AL: 8, KY: 8, LA: 8, MS: 8,
IL: 9, WI: 9,
CO: 10, MN: 10, ND: 10, SD: 10, WY: 10,
IN: 11, OH: 11, MI: 11,
ID: 12, OR: 12, UT: 12, WA: 12,
AZ: 13, HI: 13, NV: 13,
CA: 14
};
if(StructKeyExists (states_divisions_map, state) ){
division = states_divisions_map[state];
}
writeDump(var: states_divisions_map, label: "States");
writeOutput("State: #state#");
writeOutput("Division: #division#");
</cfscript>
This way you can quickly check for the state without all the loops.
Observation: In your original code, DE is present twice.
ListContains() is like ListFind(), but it searches for a list element that contains the the text you're looking for anywhere in it. (ListFind() looks for a list element that entirely matches the string. Generally ListFind() is the better tool but not in this case.
(CF does have an ArrayContains, but it doesn't work for this task, it merely tells you if the array contains an exactly matched element.)
Because you're searching for unique two-char state-codes, this is a perfect use of the function.
<cfset aStates = ArrayNew(1)>
<cfset aStates[1] = "MA,ME,NH,RI,VT">
<cfset aStates[2] = "CT,DE,NJ,NY,DE">
<cfset aStates[3] = "DC,MD,VA,WV">
<cfset aStates[4] = "TN">
<cfset aStates[5] = "NC,SC">
<cfset aStates[6] = "GA">
<cfset aStates[7] = "FL">
<cfset aStates[8] = "AL,KY,LA,MS">
<cfset aStates[9] = "IL,WI">
<cfset aStates[10] = "CO,MN,ND,SD,WY">
<cfset aStates[11] = "IN,OH,MI">
<cfset aStates[12] = "ID,OR,UT,WA">
<cfset aStates[13] = "AZ,HI,NV">
<cfset aStates[14] = "CA">
<cfset lstates = arraytolist(astates,"|")>
<cfset division = listcontainsnocase(lstates,"CO","|")>
<cfoutput>Division: #division#<br>
State: #state#</cfoutput>
We set the delimiter to | (pipe), but you can use anything, other than the default (comma), because you're using that for you're sub-delimiter.
For future reference, if you're using CF 9 (I believe) or after, you can use array shorthand for quickly building arrays.
<cfset aStates=["MA,ME,NH,RI,VT","CT,DE,NJ,NV,DE","DC,MD,VA,WV"]> ...
While it may seem merely like a stylistic difference, it saves a lot of time.
Structures can be created similarly.
<cfset MyDogIs = {Size = "medium", Color = "Black", HasPaws = ["FL","FR","BL","BR"]}>
(And you can nest implicit array and structs within another!)
Thank you all for your input. I am answering this question myself with the code I decided to use since it is based on piece supplied by multiple answers/comments. I hope this is the correct way to do this, if not please advise.
The divisions were not stored in the db as they are a set it and forget it mapping that has not changed in years and I thought I'd save a call to the DB. If they were subject to change I would have taken a table approach suggested by #leigh
Thanks to #matt-busche for the improved loop, ucase() and break recommendations, and #cfqueryparam for the short hand array suggestion.
Here's what I went with. The code is actually a function in a cfc that processes a form submission but I have pasted only the relevant part.
<cfset form.division = 15>
<cfset aDivisions = ["MA,ME,NH,RI,VT",
"CT,DE,NJ,NY,DE",
"DC,MD,VA,WV",
"TN",
"NC,SC",
"GA",
"FL",
"AL,KY,LA,MS",
"IL,WI",
"CO,MN,ND,SD,WY",
"IN,OH,MI",
"ID,OR,UT,WA",
"AZ,HI,NV",
"CA"]>
<cfloop from="1" to="#ArrayLen(aDivisions)#" index="i">
<cfif ListFind(aDivisions[i],Ucase(arguments.sForm.state)) NEQ 0>
<cfset form.division = i>
<cfbreak>
</cfif>
</cfloop>

How to (selectively) convert array elements to a list of values

Given the array below, how do I convert it to a simple list of values?
<cfdump var="#arguments.ServiceTextArray#">
Array Elements:
1 3567_no
2 3584_yes
3 3642_yes
4 3643_yes
5 3644_no
6 3645_no
7 3646_no
Specifically, how do I extract values with the "yes" suffix and produce a list like this?
3584,3642,3643
Thanks in advance.
Would this help? There will always be better solution than this.
<cfset myList = "">
<cfloop from="1" to="#Arraylen(myArray)#" index="index">
<Cfif right(myArray[index],3) EQ "yes">
<cfset myList = listAppend(myList, listFirst(myArray[index], '_'))>
</Cfif>
</cfloop>
<cfoutput>#myList#</cfoutput>
This is partly an extension to Henry answer but should be exactly what your after:
<cfscript>
tmpArray = ['567_no','584_yes','3642_yes','3643_yes','3644_no','3645_no','3646_no'];
list = "";
for (item in tmpArray)
if (listLast(item, "_") == "yes")
list = listAppend(list, listFirst(item, "_"));
writeDump(list);
var list = "";
for (item in array)
if (ListLast(item, "_"))
list = listAppend(list, val(item));
Using the Underscore.cfc library (CF 10 only):
filteredArray = _.filter(arguments.ServiceTextArray, function(val) {
return (val contains 'yes');
});
resultArray = _.map(filteredArray, function(val) {
return left(val, 4);
});
list = arrayToList(resultArray);
(I created this library, BTW)

Obtaining browser and version using Coldfusion

Is there something built into Coldfusion that would let me know the browser and version number surfing my site? Are there existant libraries that would enable me to do this?
My google-fu hasn't shown many things other than simple if statements detecting IE.
I did do a dump of the cgi.http_user_agent variable in Chrome:
Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.77 Safari/534.24
There's three different browsers mentionned in there! Doing an if statement looking for either would return true. There's got to be a more formal approach?
This is much easier than the complicated function submitted earlier...
<CFIF FindNoCase('Chrome','#CGI.HTTP_USER_AGENT#') GREATER THAN 0>
<CFSET browser = 'Chrome'>
</CFIF>
credit: http://www.paulsilver.co.uk/code/coldfusion_detect_browser.php
Could you not use javascript instead? http://cssuseragent.org/
CSSUA is awesome, adds simple classes to the tag and allows you to target content in CSS/Javascript using classes like ua-ie-6 etc
There is a User-Defined Function which can parse out the user_agent string.
browserDetect
<cfscript>
/**
* Detects 130+ browsers.
* v2 by Daniel Harvey, adds Flock/Chrome and Safari fix.
*
* #param UserAgent User agent string to parse. Defaults to cgi.http_user_agent. (Optional)
* #return Returns a string.
* #author John Bartlett (jbartlett#strangejourney.net)
* #version 4, June 28, 2009
*/
function browserDetect() {
// Default User Agent to the CGI browser string
var UserAgent=CGI.HTTP_USER_AGENT;
// Regex to parse out version numbers
var VerNo="/?v?_? ?v?[\(?]?([A-Z0-9]*\.){0,9}[A-Z0-9\-.]*(?=[^A-Z0-9])";
// List of browser names
var BrowserList="";
// Identified browser info
var BrowserName="";
var BrowserVer="";
// Working variables
var Browser="";
var tmp="";
var tmp2="";
var x=0;
// If a value was passed to the function, use it as the User Agent
if (ArrayLen(Arguments) EQ 1) UserAgent=Arguments[1];
// Allow regex to match on EOL and instring
UserAgent=UserAgent & " ";
// Browser List (Allows regex - see BlackBerry for example)
BrowserList="1X|Amaya|Ubuntu APT-HTTP|AmigaVoyager|Android|Arachne|Amiga-AWeb|Arora|Bison|Bluefish|Browsex|Camino|Check&Get|Chimera|Chrome|Contiki|cURL|Democracy|" &
"Dillo|DocZilla|edbrowse|ELinks|Emacs-W3|Epiphany|Galeon|Minefield|Firebird|Phoenix|Flock|IceApe|IceWeasel|IceCat|Gnuzilla|" &
"Google|Google-Sitemaps|HTTPClient|HP Web PrintSmart|IBrowse|iCab|ICE Browser|Kazehakase|KKman|K-Meleon|Konqueror|Links|Lobo|Lynx|Mosaic|SeaMonkey|" &
"muCommander|NetPositive|Navigator|NetSurf|OmniWeb|Acorn Browse|Oregano|Prism|retawq|Shiira Safari|Shiretoko|Sleipnir|Songbird|Strata|Sylera|" &
"ThunderBrowse|W3CLineMode|WebCapture|WebTV|w3m|Wget|Xenu_Link_Sleuth|Oregano|xChaos_Arachne|WDG_Validator|W3C_Validator|" &
"Jigsaw|PLAYSTATION 3|PlayStation Portable|IPD|" &
"AvantGo|DoCoMo|UP.Browser|Vodafone|J-PHONE|PDXGW|ASTEL|EudoraWeb|Minimo|PLink|NetFront|Xiino|";
// Mobile strings
BrowserList=BrowserList & "iPhone|Vodafone|J-PHONE|DDIPocket|EudoraWeb|Minimo|PLink|Plucker|NetFront|PIE|Xiino|" &
"Opera Mini|IEMobile|portalmmm|OpVer|MobileExplorer|Blazer|MobileExplorer|Opera Mobi|BlackBerry\d*[A-Za-z]?|" &
"PPC|PalmOS|Smartphone|Netscape|Opera|Safari|Firefox|MSIE|HP iPAQ|LGE|MOT-[A-Z0-9\-]*|Nokia|";
// No browser version given
BrowserList=BrowserList & "AlphaServer|Charon|Fetch|Hv3|IIgs|Mothra|Netmath|OffByOne|pango-text|Avant Browser|midori|Smart Bro|Swiftfox";
// Identify browser and version
Browser=REMatchNoCase("(#BrowserList#)/?#VerNo#",UserAgent);
if (ArrayLen(Browser) GT 0) {
if (ArrayLen(Browser) GT 1) {
// If multiple browsers detected, delete the common "spoofed" browsers
if (Browser[1] EQ "MSIE 6.0" AND Browser[2] EQ "MSIE 7.0") ArrayDeleteAt(Browser,1);
if (Browser[1] EQ "MSIE 7.0" AND Browser[2] EQ "MSIE 6.0") ArrayDeleteAt(Browser,2);
tmp2=Browser[ArrayLen(Browser)];
for (x=ArrayLen(Browser); x GTE 1; x=x-1) {
tmp=Rematchnocase("[A-Za-z0-9.]*",Browser[x]);
if (ListFindNoCase("Navigator,Netscape,Opera,Safari,Firefox,MSIE,PalmOS,PPC",tmp[1]) GT 0) ArrayDeleteAt(Browser,x);
}
if (ArrayLen(Browser) EQ 0) Browser[1]=tmp2;
}
// Seperate out browser name and version number
tmp=Rematchnocase("[A-Za-z0-9. _\-&]*",Browser[1]);
Browser=tmp[1];
if (ArrayLen(tmp) EQ 2) BrowserVer=tmp[2];
// Handle "Version" in browser string
tmp=REMatchNoCase("Version/?#VerNo#",UserAgent);
if (ArrayLen(tmp) EQ 1) {
tmp=Rematchnocase("[A-Za-z0-9.]*",tmp[1]);
BrowserVer=tmp[2];
}
// Handle multiple BlackBerry browser strings
if (Left(Browser,10) EQ "BlackBerry") Browser="BlackBerry";
// Return result
return Browser & " " & BrowserVer;
}
// Unable to identify browser
return "Unknown";
}
</cfscript>
I created BrowscapCFC powered by http://browscap.org/. It may fit your needs
There's nothing built into ColdFusion that will allow you to do this. Capturing the user_agent string and parsing it manually is about the best you can do.
There are third party apps like Browserhawk (http://www.cyscape.com/Default.aspx) that will do this... and while I've not used Browserhawk myself, I understand that it does the job well.
Googling also turns up http://www.andrewdixon.co.uk/aebrowser/, but that is 6 years old. Might still work... but depending on how bulletproof you need the solution to be, you might want to consider something like Browserhawk. You could google for a list of user agent strings and handle it yourself... but given the sheer number of browsers (and versions) across OS's, I think that'd be a full time job in and of itself.
The user defined function above worked great, but I had to add the following code to get the version number to work correctly with IE (MSIE):
// Handle MSIE Version issue
IF (Left(Browser,4) EQ "MSIE") {
tmpBrowser = Browser;
IF (listLen("#tmpBrowser#", " ") EQ 2) {
Browser = listGetAt("#tmpBrowser#", 1, " ");
BrowserVer = listGetAt("#tmpBrowser#", 2, " ");
}
}
With a user_agent like this:
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)
It was returning a blank version and a browser name of "MSIE 8.0"
Here is a recent build of the browser name and version
Tested on Opra / IE 8 / IE 9 / IE 10 / IE 11 / Edge / Chrome / Firefox
<cffunction name="GetBrowser" access="public">
<cfset var l = {}>
<cfset l.UserInfo = cgi.HTTP_USER_AGENT>
<cfset l.Browser = {}>
<cfif FindNoCase ( "Opera", l.UserInfo ) >
<cfset l.Browser.type = "Opera">
<cfset l.Browser.Name = "Opera">
<cfelseif FindNoCase ( "OPR", l.UserInfo )>
<cfset l.Browser.type = "OPR">
<cfset l.Browser.Name = "Opera">
<cfelseif FindNoCase ( "Edge", l.UserInfo )>
<cfset l.Browser.type = "Edge">
<cfset l.Browser.Name = "Edge">
<cfelseif FindNoCase ( "Chrome", l.UserInfo )>
<cfset l.Browser.type = "Chrome">
<cfset l.Browser.Name = "Chrome">
<cfelseif FindNoCase ( "Safari", l.UserInfo )>
<cfset l.Browser.type = "Safari">
<cfset l.Browser.Name = "Safari">
<cfelseif FindNoCase ( "FireFox", l.UserInfo )>
<cfset l.Browser.type = "FireFox">
<cfset l.Browser.Name = "FireFox">
<cfelseif FindNoCase ( "MSIE", l.UserInfo ) >
<cfset l.Browser.type = "MSIE">
<cfset l.Browser.Name = "Internet Explorer">
<cfelseif FindNoCase ( "Trident", l.UserInfo ) >
<cfset l.Browser.type = "Trident">
<cfset l.Browser.Name = "Internet Explorer">
<cfelse>
<cfset l.Browser.type = "Unknown">
<cfset l.Browser.Name = "Unknown">
<cfset l.Browser.Version = "Unknown">
</cfif>
<cfif FindNoCase ( "rv:" , l.UserInfo ) NEQ 0>
<cfset l.Params = REFindNoCase ( "rv:[0-9.]+", l.UserInfo, Find ( "rv:", l.UserInfo ), true )>
<cfset l.FirstOccurrence = l.Params.POS[1]>
<cfset l.ParamLength = l.Params.LEN[1]>
<cfset l.BrowserNameLength = Len ( "rv:" )>
<cfset l.Browser.Version = Mid ( l.UserInfo, ( l.FirstOccurrence + l.BrowserNameLength ), ( l.ParamLength - l.BrowserNameLength ) )>
<cfelse>
<cfset l.Params = REFindNoCase ( #l.Browser.type# & "/?[ 0-9.]+", l.UserInfo, Find ( "#l.Browser.type#", l.UserInfo ), true )>
<cfset l.FirstOccurrence = l.Params.POS[1]>
<cfset l.ParamLength = l.Params.LEN[1]>
<cfset l.BrowserNameLength = Len ( l.Browser.type ) + 1>
<cfset l.Browser.Version = Mid ( l.UserInfo, ( l.FirstOccurrence + l.BrowserNameLength ), ( l.ParamLength - l.BrowserNameLength ) )>
</cfif>
<cfreturn l.Browser>
</cffunction>
<cfdump var="#GetBrowser()#">
Here is refactored (from above) cfscript version for just browser name
function GetBrowserName() {
var rs = "Unknown";
var uag = cgi.HTTP_USER_AGENT;
if (FindNoCase("Opera",uag))
rs = "Opera";
else if (FindNoCase("OPR",uag))
rs = "Opera";
else if (FindNoCase("Edge",uag))
rs = "Edge";
else if (FindNoCase("Chrome",uag))
rs = "Chrome";
else if (FindNoCase("Safari",uag))
rs = "Safari";
else if (FindNoCase("Firefox",uag))
rs = "Firefox";
else if (FindNoCase("MSIE",uag))
rs = "IExplore";
else if (FindNoCase("Trident",uag))
rs = "IExplore";
else
rs = "Unknown";
return rs;
}