otsukare Thoughts after a day of work

Encyclopedia Of Broken UserAgent String Detections

Did you detect the right user agent string?

cat hidden in a bamboo forest.

This is not a comprehensive encyclopedia, but these are patterns we have met in the past for identifying user agent strings which are broken or future fail.

Do not use these ! and if your code is using one form of these, please change it. Tell me if you found new ones.

Comparing Strings Instead Of Numbers

This was explained in details in Slack is optimized for Firefox version 520

The version number of the userAgent is extracted as a string, not an integer.

var browser_version = "100";
var support_min_version = "90";
if (browser_version < support_min_version) {
  console.log("too old");
} else {
  console.log("supported");
}
// too old instead of supported as a result

A better pattern here is to use integer

var browser_version = parseInt("100", 10) // the "100" as a string came from a detection early on
var support_min_version = 90; // integer not a string;
if (browser_version < support_min_version) {
  console.log("too old");
} else {
  console.log("supported");
}
// too old instead of supported as a result

Substring Slicing According To Position

The assumption here is that the substring representing the number is two characters after the slash. The 8 is for Firefox/

ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0"
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0"
start = ua.indexOf('Firefox')
// 67
version = ua.substring(start + 8, start + 10)
// "10"

A better pattern for this one is:

ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0"
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0"
start = ua.indexOf('Firefox')
// 67
ua.substring(start + 'Firefox'.length + 1)
// "100.0"
parseFloat(ua.substring(start + 'Firefox'.length + 1))
// 100

Regex Matching Exactly Two Digits

This is a fairly common mistake, most of the detection algorithm have been fixed when browsers switched their versions from one digit to two digits, but there is still code out there relying on fixed lengths.

const ua_string = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0";
const ua_100 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0";
ua_string.match(/Firefox\/(\d\d)/); //  ["Firefox/91", "91"]
ua_string.match(/Firefox\/(\d{2})/); // ["Firefox/91", "91"]
ua_string.match(/Firefox\/(\d\d)\./); //  ["Firefox/91.", "91"]
ua_100.match(/Firefox\/(\d\d)/); //  ["Firefox/10", "10"]
ua_100.match(/Firefox\/(\d{2})/); // ["Firefox/10", "10"]
ua_100.match(/Firefox\/(\d\d)\./); //  null

A better pattern would be

ua_string.match(/Firefox\/(\d+)/); //  ["Firefox/91", "91"]
ua_string.match(/Firefox\/(\d+)/); //  ["Firefox/100", "100"]

Detecting Firefox on iOS as Android.

Many sites have a grid for the minimum version number supported for each browsers. The current Firefox User Agent string on iOS has this pattern.

Mozilla/5.0 (iPhone; CPU OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/33.0 Mobile/15E148 Safari/605.1.15

Notice that FxiOS/33.0 means Firefox on iOS version 33. Web developers often used a up to date library that will return something like:

{
  "ua": "Mozilla/5.0 (iPhone; CPU OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/33.0 Mobile/15E148 Safari/605.1.15",
  "browser": {
    "name": "Firefox",
    "version": "33.0",
    "major": "33"
  },
  "engine": {
    "name": "WebKit",
    "version": "605.1.15"
  },
  "os": {
    "name": "iOS",
    "version": "14.4.2"
  },
  "device": {
    "vendor": "Apple",
    "model": "iPhone",
    "type": "mobile"
  },
  "cpu": {},
  "gpu": {}
}

So far so good. The issue starts when the site says: "Oh. It's Firefox on Mobile, so it means this is Android. We support Firefox on Android starting Firefox/78". Then they block the access to the full site, or the video, or asking the user to upgrade their browsers while they are on the latest version of Firefox iOS.

A better pattern is to detect the OS and/or the engine this browser is working on, and adjust your support matrix. An even better pattern is to have the site handling graceful degradation whichever the version of the browser.

Comments

If you have more questions, things I may have missed, different take on them. Feel free to comment…. Be mindful.

Otsukare!