Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'v2.0'

Conflicts:
	package.json
	tests/browser.js
	tests/parser.js
	useragent.js
  • Loading branch information...
commit e5c77371a2532d3b5ed1c9fb58c4a07db95cc361 2 parents a6798b4 + b70a0ea
@3rd-Eden authored
View
2  .gitignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
View
3  .npmignore
@@ -0,0 +1,3 @@
+tests
+benchmark
+node_modules
View
38 LICENSE
@@ -0,0 +1,38 @@
+The regex library that the useragent parser uses if from; code.google.com/p/ua-parser/
+which is released under Apache license:
+
+# Copyright 2009 Google Inc.
+#
+# 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.
+
+The node library is released under MIT license:
+
+# Copyright (c) 2011 Observer (http://observer.no.de) <info@3rd-Eden.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
View
16 LICENSE.txt
@@ -1,16 +0,0 @@
-This parser is based on the user agent parser; code.google.com/p/ua-parser/
-which is released under Apache license:
-
-# Copyright 2009 Google Inc.
-#
-# 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.
View
21 Makefile
@@ -0,0 +1,21 @@
+ALL_TESTS = $(shell find tests/ -name '*.test.js')
+ALL_QA = $(shell find tests/ -name '*.qa.js')
+
+run-tests:
+ @./node_modules/.bin/expresso \
+ -t 4000 \
+ -I support \
+ -I lib \
+ $(TESTFLAGS) \
+ $(TESTS)
+
+test:
+ @$(MAKE) TESTS="$(ALL_TESTS)" run-tests
+
+qa:
+ @$(MAKE) TESTS="$(ALL_QA)" run-tests
+
+test-cov:
+ @TESTFLAGS=--cov $(MAKE) test
+
+.PHONY: test
View
278 README.md
@@ -1,43 +1,259 @@
-#node-useragent
+# useragent
-A user agent string parser for Node.js. Based on the Browserscope.org UA parser so we can actually get accurate version numbers of the browsers
-instead parsing out the render engine version numbers.
+Useragent is a port of browserscope.org's user agent parser project which
+allows you to parse user agent strings with high accuracy by using hand tuned
+dedicated regular expressions for browser matching. Regular user agent parser
+have major issues because they usually parse out the version number of the
+render engine instead of the version number of the browser.
-## Installation
+Because user agent parsing will always be like shooting a moving target because
+browser vendors keep making subtle changes to them it's important to keep your
+the regular expressions database up to date. When you install useragent it will
+automatically download the latest regexp database from the ua-parser project
+and transform it in to a dedicated node.js require statement. This way you will
+always be up to date.
-Using the npm package manager
+But there few more tricks, so keep reading on until you hit the API section.
- npm install useragent
-
+### Performance
-Through git
+The 1.0 release of the useragent parser spots a major performance improvement
+of the old parser, it also has a reduced memory level because we recompiled the
+regex list to make a smaller impact.
- git clone git://github.com/3rd-Eden/node-useragent.git
-
-
+Some stats from the benchmark:
-## What can it do
+> Starting the benchmark, parsing 47 useragent strings per run
+> Executed benchmark (useragent2)
+> Count (86), Cycles (96), Elapsed (5.028), Hz (1640.9779913574882)
+>
+> Executed benchmark (useragent1)
+> Count (56), Cycles (96), Elapsed (5.03), Hz (1063.7910527195145)
-I could write a whole story about it, but just fire check out the example `node example.js` and navigate to `http://127.0.0.1:8000/`
-In your browser you will see the following ( if you are using Chrome like I did ):
+As you can see that useragent 2 (1.0) is much faster then original parser. The
+test above was executed on a iMac 2010 and Node.js v0.4.12. The code has been
+optimized to take full advantage of the upcoming V8 crankshaft support in
+node.js and you will another performance boost from it. Free performance boost,
+so that is pretty bad ass.
-Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.559.0 Safari/534.10
+### Installation
-userAgent.is
- `{"version":"534.10","webkit":true,"opera":false,"ie":false,"firefox":true,"safari":true,"mobile_safari":false}`
-userAgent.parser
- `{"family":"Chrome","V1":"8","V2":"0","V3":"559","match":"Chrome/8.0.559","os":{"match":"OS X 10_6_4","family":"OS X","V1":"10","V2":"6","V3":"4"}}`
-userAgent response.pretty
- `Chrome 8.0.559`
-userAgent response.prettyOs
- `OS X 10.6.4`
+Installation is done using the Node Package Manager (npm). If you don't have
+npm installed on your system you can download it from
+[npmjs.org](http://npmjs.org)
-As you can see in the example above, there are different ways of parsing the user-agent string. Usually using .browser() would be enough to do some low level user-agent targeting. It parses it in a way that most client side libraries would parse the user-agent. This is however not that accurate. As you can see in the in the output above the .browser() tell us the version if 533.8, but the real version of the browser is 4.0.4.
+```
+npm install useragent
+```
-The .parser() generates a user_agent object, this contains the:
-- Browser / Family name.
-- Version number information ( V1.V2.V3 ).
-- Os object, contains the Family and version numbers.
-- Matched string of the user agent.
-- pretty(), a method that prints out the browser in pretty string.
-- prettyOs, a method that prints out the operating system in a pretty string.
+### API / Working the codez
+
+Include the useragent parser in you node.js application:
+
+```js
+var useragent = require('useragent');
+```
+
+The useragent library allows you do use the automatically installed regex
+library or you can fetch it live from the remote servers. So if you are
+paranoid and always want your regex library to be up to date to match with
+agent the widest range of useragent strings you can do:
+
+```js
+var useragent = require('useragent');
+useragent(true);
+```
+
+This will async load the database from the server and compile it to a proper
+JavaScript supported format. If it fails to compile or load it from the remote
+location it will just fallback silently to the shipped version.
+
+But there are more ways to update your regex library, when the useragent is
+installed on your system we automatically start a update process to see if the
+shipped version is out of date or not, if we can fetch one we will store that
+one instead so you have latest version available for you when you install.
+
+In addition to this, doing a `npm update useragent` should also re-fetch the
+library for you. So many ways to stay up to date ;).
+
+#### useragent.is(useragent string).browsername;
+
+This api provides you with a quick and dirty browser lookup. The underlying
+code is usually found on client side scripts so it's not the same quality as
+our parse method but it does the job.
+
+`useragent.is` returns a object with potential matched browser names
+
+```js
+useragent.is('ua string').firefox // true
+useragent.is('ua string').safari // false
+var ua = useragent.is('ua string')
+
+// the object
+{
+ version: '3'
+ webkit: false
+ opera: false
+ ie: false
+ chrome: false
+ safari: false
+ mobile_safari: false
+ firefox: true
+}
+```
+
+#### useragent.parse(useragent string[, js useragent]);
+
+This is the actual user agent parser, this is where all the magic is happening.
+The function accepts 2 arguments, both should be a `string`. The first argument
+should the useragent string that is known on the server from the
+`req.headers.useragent` header. The other argument is optional and should be
+the useragent string that you see in the browser, this can be send from the
+browser using a xhr request or something like this. This allows you detect if
+the user is browsing the web using the `Chrome Frame` plugin.
+
+The parser returns a Agent instance, this allows you to output user agent
+information in different predefined formats. See the Agent section for more
+information.
+
+```js
+var agent = useragent.parse(req.headers.useragent);
+
+// example for parsing both the useragent header and a optional js useragent
+var agent2 = useragent.parse(req.headers.useragent, req.query.jsuseragent);
+```
+
+#### useragent.lookup(useragent string[, js useragent]);
+
+This provides the same functionality as above, but it caches the useragent
+string and it's parsed result in memory to provide faster lookups in the
+future. This can be handly if you expect to parse a lot of user agent strings.
+
+It uses the same arguments as the `useragent.parse` method and returns exactly
+the same result, but it's just cached.
+
+#### useragent.fromString(agent.toString());
+
+Parses the result of `agent.toString()` back to a new Agent instance.
+
+```js
+var agent = useragent.parse(req.headers.useragent)
+ , another = useragent.fromString(agent.toString());
+
+console.log(agent == another);
+```
+
+#### useragent.fromJSON(agent.toJSON());
+
+Parses the result of `agent.toJSON()` back to a new Agent instance.
+
+```js
+var agent = useragent.parse(req.headers.useragent)
+ , another = useragent.fromJSON(agent.toJSON());
+
+console.log(agent == another);
+```
+
+### Agents
+
+Most of the methods mentioned above return a Agent instance. This Agent exposes
+the parsed out information from the user agent strings. This allows us to
+extend the agent with more methods that do not nessesarly need to be in the
+core agent instance, allowing us to expose a plugin interface for third party
+developers.
+
+You can read out the following properties:
+
+* `family` The browser family, or browser name, it defaults to Other.
+* `major` The major version number of the family, it defaults to 0.
+* `minor` The minor version number of the family, it defaults to 0.
+* `patch` The patch version number of the family, it defaults to 0.
+* `os` The operating system of the user, it defaults to Other.
+
+While most version number information is a string, I have chosen to present
+them all as string because a browser can also be identified as beta or alpha.
+When the family name or os can not be determined we will default to the string
+Other.
+
+The following methods are available:
+
+#### Agent.toAgent();
+
+Returns the family and version number concatinated in a nice human readable
+string.
+
+```js
+var agent = useragent.parse(req.headers.useragent);
+agent.toAgent(); // 'Chrome 15.0.874'
+```
+
+#### Agent.toString();
+
+Returns the results of the `Agent.toAgent()` but also adds the parsed operating
+system to the string in a human readable format.
+
+```js
+var agent = useragent.parse(req.headers.useragent);
+agent.toString(); // 'Chrome 15.0.874 / Mac OS X'
+
+// as it's a to string method you can also concat it with another string
+
+'your useragent is ' + agent; // 'your useragent is Chrome 15.0.874 / Mac OS X'
+```
+
+#### Agent.toVersion();
+
+Returns the version of the browser in a human readable string.
+
+```js
+var agent = useragent.parse(req.headers.useragent);
+agent.toVersion(); // '15.0.874'
+```
+
+#### Agent.toJSON();
+
+Generates a stringified JSON output of the agent, this can later be used again
+to create a new Agent instance using the `useragent.fromJSON` method.
+
+```js
+var agent = useragent.parse(req.headers.useragent);
+agent.toJSON(); //'{"family":"Chrome","major":"15","minor":"0","patch":"874","os":"Mac OS X"}'
+```
+
+### Adding more features to the Agent
+
+As I wanted to keep the core of the useragent parser as clean and fast as
+possible I decided to move some of the initally planned features to a new
+`plugin` file.
+
+These extenstions to the Agent prototype can be loaded by requiring the
+`useragent/features` file:
+
+```js
+var useragent = require('useragent');
+require('useragent/features');
+```
+
+The inital release introduces 1 new method, satisfies, which allows you to see
+if the version number of the browser satisfies a certain range. It uses the
+semver library to do all the range calculations but here is a small summary of
+the supported range styles:
+
+* `>1.2.3` Greater than a specific version.
+* `<1.2.3` Less than.
+* `1.2.3 - 2.3.4` := `>=1.2.3 <=2.3.4`.
+* `~1.2.3` := `>=1.2.3 <1.3.0`.
+* `~1.2` := `>=1.2.0 <2.0.0`.
+* `~1` := `>=1.0.0 <2.0.0`.
+* `1.2.x` := `>=1.2.0 <1.3.0`.
+* `1.x` := `>=1.0.0 <2.0.0`.
+
+#### Agent.satisfies('range style here');
+
+Check if the agent matches the supplied range.
+
+```js
+var agent = useragent.parse(req.headers.useragent);
+agent.satisfies('15.x || >=19.5.0 || 25.0.0 - 17.2.3'); // true
+agent.satisfies('>16.12.0'); // false
+```
View
71 benchmark/dict.js
@@ -0,0 +1,71 @@
+/**
+ * Benchmark dependencies
+ */
+
+var microtime = require('microtime')
+ , benchmark = require('benchmark')
+ , yaml = require('yamlparser')
+ , path = require('path')
+ , fs = require('fs');
+
+/**
+ * Useragent parsers
+ */
+
+var useragent2 = require('../')
+ , useragent = require('useragent')
+ , uaparser = require('ua-parser');
+
+/**
+ * Setup the test-files
+ */
+
+var useragentlist = path.join(__dirname, '..', 'tests', 'fixtures', 'testcases.yaml')
+ , yammy = yaml.eval(fs.readFileSync(useragentlist).toString()).test_cases
+ , testcases = yammy.map(function (test) { return test.user_agent_string })
+ , length = testcases.length;
+
+/**
+ * Setup the benchmark
+ */
+
+var froomfroom = new benchmark.Suite;
+
+froomfroom.add('useragent2', function () {
+ for (var i = 0; i < length; i++ ) {
+ useragent2.parse(testcases[i]);
+ }
+})
+froomfroom.add('useragent2 dictionary', function () {
+ for (var i = 0; i < length; i++ ) {
+ useragent2.lookup(testcases[i]);
+ }
+})
+.add('useragent1', function () {
+ for (var i = 0; i < length; i++ ) {
+ useragent.parser(testcases[i]);
+ }
+})
+.add('ua-parser', function () {
+ for (var i = 0; i < length; i++ ) {
+ uaparser.parse(testcases[i]);
+ }
+})
+.on('cycle', function (bench, details) {
+ console.log('Executed benchmark (%s)', details.name);
+ console.log('Count (%d), Cycles (%d), Elapsed (%d), Hz (%d)\n'
+ , details.count
+ , details.cycles
+ , details.times.elapsed
+ , details.hz
+ );
+})
+.on('complete', function () {
+ console.log(this.filter('fastest').pluck('name') + ' has/have the fastest parser');
+});
+
+/**
+ * Start the benchmark, froom frooom!!
+ */
+console.log('Starting the benchmark, parsing ' + length + ' useragent strings per run');
+froomfroom.run();
View
11 benchmark/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "ua-benchmarker"
+ , "description": "benchmarks the different user agent parsers to see their parse performance"
+ , "version": "0.0.0"
+ , "dependencies": {
+ "microtime": ""
+ , "benchmark": ""
+ , "useragent": "0.1.2"
+ , "ua-parser": "0.2.3"
+ }
+}
View
66 benchmark/run.js
@@ -0,0 +1,66 @@
+/**
+ * Benchmark dependencies
+ */
+
+var microtime = require('microtime')
+ , benchmark = require('benchmark')
+ , yaml = require('yamlparser')
+ , path = require('path')
+ , fs = require('fs');
+
+/**
+ * Useragent parsers
+ */
+
+var useragent2 = require('../')
+ , useragent = require('useragent')
+ , uaparser = require('ua-parser');
+
+/**
+ * Setup the test-files
+ */
+
+var useragentlist = path.join(__dirname, '..', 'tests', 'fixtures', 'testcases.yaml')
+ , yammy = yaml.eval(fs.readFileSync(useragentlist).toString()).test_cases
+ , testcases = yammy.map(function (test) { return test.user_agent_string })
+ , length = testcases.length;
+
+/**
+ * Setup the benchmark
+ */
+
+var froomfroom = new benchmark.Suite;
+
+froomfroom.add('useragent2', function () {
+ for (var i = 0; i < length; i++ ) {
+ useragent2.parse(testcases[i]);
+ }
+})
+.add('useragent1', function () {
+ for (var i = 0; i < length; i++ ) {
+ useragent.parser(testcases[i]);
+ }
+})
+.add('ua-parser', function () {
+ for (var i = 0; i < length; i++ ) {
+ uaparser.parse(testcases[i]);
+ }
+})
+.on('cycle', function (bench, details) {
+ console.log('Executed benchmark (%s)', details.name);
+ console.log('Count (%d), Cycles (%d), Elapsed (%d), Hz (%d)\n'
+ , details.count
+ , details.cycles
+ , details.times.elapsed
+ , details.hz
+ );
+})
+.on('complete', function () {
+ console.log(this.filter('fastest').pluck('name') + ' has/have the fastest parser');
+});
+
+/**
+ * Start the benchmark, froom frooom!!
+ */
+console.log('Starting the benchmark, parsing ' + length + ' useragent strings per run');
+froomfroom.run();
View
24 bin/testfiles.js
@@ -0,0 +1,24 @@
+#!/usr/bin/env node
+
+var request = require('request')
+ , path = require('path')
+ , fs = require('fs');
+
+var files = {
+ 'pgts.yaml': 'http://ua-parser.googlecode.com/svn/trunk/test/resources/pgts_browser_list.yaml'
+ , 'testcases.yaml': 'http://ua-parser.googlecode.com/svn/trunk/test/resources/test_user_agent_parser.yaml'
+ , 'firefoxes.yaml': 'http://ua-parser.googlecode.com/svn/trunk/test/resources/firefox_user_agent_strings.yaml'
+};
+
+/**
+ * Update the fixtures
+ */
+
+Object.keys(files).forEach(function (key) {
+ request(files[key], function response (err, res, data) {
+ if (err || res.statusCode !== 200) return console.error('failed to update');
+
+ console.log('downloaded', files[key]);
+ fs.writeFileSync(path.join(__dirname, '..', 'tests', 'fixtures', key), data);
+ });
+});
View
9 bin/update.js
@@ -0,0 +1,9 @@
+#!/usr/bin/env node
+
+var updater = require('../lib/update');
+updater(function (err, data) {
+ if (err) return console.error('Update unsuccessfull due to errors', err.message);
+
+ // yay no issues
+ console.log('Successfully fetched and generated new parsers from the internets.');
+});
View
35 example.js
@@ -1,35 +0,0 @@
-var http = require( 'http' ),
- userAgent = require( './useragent' );
-
-http.createServer(function( request, response ){
-
- // creates a parsed down userAgent Object
- var userAgentString = request.headers['user-agent'],
- ua_obj = userAgent.parser( userAgentString ),
- is = userAgent.browser( userAgentString ),
- content = "";
-
- // generate some body
- content += "<h1>{ua}</h1>"
- content += "<dl>";
- content += "<dt>userAgent.is</dt><dd>{is}</dd>";
- content += "<dt>userAgent.parser</dt><dd>{ua_obj}</dd>";
- content += "<dt>userAgent response.pretty</dt><dd>{pretty}</dd>";
- content += "<dt>userAgent response.prettyOs</dt><dd>{prettyOs}</dd>";
- content += "</dl>";
-
- // write out the response
- response.writeHead( 200, {'Content-Type': 'text/html'});
- response.end(
- // easy variable replacement
- content.replace( "{ua}", userAgentString )
- .replace( "{ua_obj}", JSON.stringify( ua_obj ) )
- .replace( "{is}", JSON.stringify( is ) )
- .replace( "{pretty}", ua_obj.pretty() )
- .replace( "{prettyOs}", ua_obj.prettyOs() )
- );
-
-}).listen( 8000 );
-
-// Display connection information
-console.log( 'Server running at http://127.0.0.1:8000/' );
View
29 features/index.js
@@ -0,0 +1,29 @@
+/**
+ * Plugin dependencies
+ */
+
+var Agent = require('../lib/useragent').Agent
+ , semver = require('semver');
+
+/**
+ * Fetches the features of the browser from the browserscope knowledge base.
+ *
+ * @api public
+ */
+
+Agent.prototype.__defineGetter__('features', function () {
+
+});
+
+/**
+ * Checks if the user agent's version can be satisfied agents the give
+ * ranged argument. This uses the semver libraries range construction.
+ *
+ * @param {String} ranged The range the version has to satisfie
+ * @returns {Boolean}
+ * @api public
+ */
+
+Agent.prototype.satisfies = function satisfies (range) {
+ return semver.satisfies(this.major + '.' + this.minor + '.' + this.patch, range);
+};
View
1  index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/useragent');
View
1  lib/agents.js
@@ -0,0 +1 @@
+exports.browser = [{r:new RegExp("^(Opera)/(\\d+)\\.(\\d+) \\(Nintendo Wii"), family:"Wii"},{r:new RegExp("(SeaMonkey|Fennec|Camino)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*)")},{r:new RegExp("(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre)?)"), family:"Firefox ($1)"},{r:new RegExp("(Firefox)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)"), family:"Firefox Beta"},{r:new RegExp("(Firefox)-(?:\\d+\\.\\d+)?/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)"), family:"Firefox Beta"},{r:new RegExp("(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)?"), family:"Firefox ($1)"},{r:new RegExp("(Firefox).*Tablet browser (\\d+)\\.(\\d+)\\.(\\d+)"), family:"MicroB"},{r:new RegExp("(MozillaDeveloperPreview)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)?")},{r:new RegExp("(Flock)/(\\d+)\\.(\\d+)(b\\d+?)")},{r:new RegExp("(RockMelt)/(\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(Fennec)/(\\d+)\\.(\\d+)(pre)")},{r:new RegExp("(Navigator)/(\\d+)\\.(\\d+)\\.(\\d+)"), family:"Netscape"},{r:new RegExp("(Navigator)/(\\d+)\\.(\\d+)([ab]\\d+)"), family:"Netscape"},{r:new RegExp("(Netscape6)/(\\d+)\\.(\\d+)\\.(\\d+)"), family:"Netscape"},{r:new RegExp("(MyIBrow)/(\\d+)\\.(\\d+)"), family:"My Internet Browser"},{r:new RegExp("(Opera Tablet).*Version\\/(\\d+)\\.(\\d+)(?:\\.(\\d+))?")},{r:new RegExp("(Opera)/.+Opera Mobi.+Version/(\\d+)\\.(\\d+)"), family:"Opera Mobile"},{r:new RegExp("(Opera Mini)/(\\d+)\\.(\\d+)")},{r:new RegExp("(Opera)/9.80.*Version\\/(\\d+)\\.(\\d+)(?:\\.(\\d+))?")},{r:new RegExp("(webOS)/(\\d+)\\.(\\d+)"), family:"Palm webOS"},{r:new RegExp("(luakit)"), family:"LuaKit"},{r:new RegExp("(Lightning)/(\\d+)\\.(\\d+)([ab]?\\d+[a-z]*)")},{r:new RegExp("(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre)?) \\(Swiftfox\\)"), family:"Swiftfox"},{r:new RegExp("(Firefox)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)? \\(Swiftfox\\)"), family:"Swiftfox"},{r:new RegExp("rekonq"), family:"Rekonq"},{r:new RegExp("(conkeror|Conkeror)/(\\d+)\\.(\\d+)\\.?(\\d+)?"), family:"Conkeror"},{r:new RegExp("(konqueror)/(\\d+)\\.(\\d+)\\.(\\d+)"), family:"Konqueror"},{r:new RegExp("(PlayBook).+RIM Tablet OS (\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(WeTab)-Browser")},{r:new RegExp("(wOSBrowser).+TouchPad/(\\d+)\\.(\\d+)"), family:"webOS TouchPad"},{r:new RegExp("(Comodo_Dragon)/(\\d+)\\.(\\d+)\\.(\\d+)"), family:"Comodo Dragon"},{r:new RegExp("(YottaaMonitor)")},{r:new RegExp("(Kindle)/(\\d+)\\.(\\d+)")},{r:new RegExp("(AdobeAIR|Chromium|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Shiira|Sunrise|Chrome|Flock|Netscape|Lunascape|Epiphany|WebPilot|Vodafone|NetFront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iron|Iris)/(\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(Bolt|Jasmine|IEMobile|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Chrome|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|Vodafone|NetFront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|NetNewsWire|Iron|Space Bison|Stainless|Orca|Dolfin|BOLT)/(\\d+)\\.(\\d+)")},{r:new RegExp("(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(iCab|Lunascape|Opera|Android) (\\d+)\\.(\\d+)\\.?(\\d+)?")},{r:new RegExp("(IEMobile) (\\d+)\\.(\\d+)"), family:"IE Mobile"},{r:new RegExp("(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(Firefox)/(\\d+)\\.(\\d+)(pre|[ab]\\d+[a-z]*)?")},{r:new RegExp("(Obigo|OBIGO)[^\\d]*(\\d+)(?:.(\\d+))?"), family:"Obigo"},{r:new RegExp("(MAXTHON|Maxthon) (\\d+)\\.(\\d+)"), family:"Maxthon"},{r:new RegExp("(Maxthon|MyIE2|Uzbl|Shiira)"), major: "0"},{r:new RegExp("(PLAYSTATION) (\\d+)"), family:"PlayStation"},{r:new RegExp("(PlayStation Portable)[^\\d]+(\\d+).(\\d+)")},{r:new RegExp("(BrowseX) \\((\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(POLARIS)/(\\d+)\\.(\\d+)"), family:"Polaris"},{r:new RegExp("(BonEcho)/(\\d+)\\.(\\d+)\\.(\\d+)"), family:"Bon Echo"},{r:new RegExp("(iPod).+Version/(\\d+)\\.(\\d+)\\.(\\d+)")},{r:new RegExp("(iPhone) OS (\\d+)_(\\d+)(?:_(\\d+))?")},{r:new RegExp("(iPad).+ OS (\\d+)_(\\d+)(?:_(\\d+))?")},{r:new RegExp("(Avant)"), major: "1"},{r:new RegExp("(Nokia)[EN]?(\\d+)")},{r:new RegExp("(Black[bB]erry).+Version\\/(\\d+)\\.(\\d+)\\.(\\d+)"), family:"Blackberry"},{r:new RegExp("(Black[bB]erry)\\s?(\\d+)"), family:"Blackberry"},{r:new RegExp("(OmniWeb)/v(\\d+)\\.(\\d+)")},{r:new RegExp("(Blazer)/(\\d+)\\.(\\d+)"), family:"Palm Blazer"},{r:new RegExp("(Pre)/(\\d+)\\.(\\d+)"), family:"Palm Pre"},{r:new RegExp("(Links) \\((\\d+)\\.(\\d+)")},{r:new RegExp("(QtWeb) Internet Browser/(\\d+)\\.(\\d+)")},{r:new RegExp("(Version)/(\\d+)\\.(\\d+)(?:\\.(\\d+))?.*Safari/"), family:"Safari"},{r:new RegExp("(Safari)/\\d+")},{r:new RegExp("(OLPC)/Update(\\d+)\\.(\\d+)")},{r:new RegExp("(OLPC)/Update()\\.(\\d+)"), major: "0"},{r:new RegExp("(SamsungSGHi560)"), family:"Samsung SGHi560"},{r:new RegExp("^(SonyEricssonK800i)"), family:"Sony Ericsson K800i"},{r:new RegExp("(Teleca Q7)")},{r:new RegExp("(MSIE) (\\d+)\\.(\\d+)"), family:"IE"}];exports.os = [{r:new RegExp("(Android)")},{r:new RegExp("(Windows)")},{r:new RegExp("(iPad)")},{r:new RegExp("(iPhone OS) ([\\d_]+)"), os:"iOS"},{r:new RegExp("(Mac OS X)")},{r:new RegExp("(FreeBSD)")},{r:new RegExp("(iPod)")},{r:new RegExp("(iPhone Simulator)")},{r:new RegExp("(Linux)")}];
View
103 lib/update.js
@@ -0,0 +1,103 @@
+var request = require('request')
+ , yaml = require('yamlparser')
+ , path = require('path')
+ , fs = require('fs')
+ , vm = require('vm');
+
+/**
+ * Fetches a new version of the user agent yaml
+ *
+ * @param {Function} fn Callback function
+ * @api public
+ */
+
+module.exports = function update (fn) {
+ // fetch it
+ request(exports.remote, function remoteResponse (err, res, data) {
+ if (err) return fn(err);
+ if (res.statusCode !== 200) return fn(new Error('incorrect status code'));
+
+ // try to see if we can parse the yaml
+ var parsed_yaml;
+
+ try { parsed_yaml = yaml.eval(data) }
+ catch (e) { return fn(e) }
+
+ // make sure we have a correct structure because we are reading this from
+ // a remote resource, they might change the structure of the JSON
+ if (
+ !('user_agent_parsers' in parsed_yaml)
+ || !parsed_yaml.user_agent_parsers.length
+ || !parsed_yaml.user_agent_parsers[0].regex
+ || !('os_parsers' in parsed_yaml)
+ || !parsed_yaml.os_parsers.length
+ || !parsed_yaml.os_parsers[0].regex
+ ) return fn(new Error('invalid yaml structure'));
+
+ // \o/ working, now to create pre-compiled version of the regexps
+ // now we can try to generate a smaller file so it will take less memory
+ // once we load thing in memory
+ var agents = parsed_yaml.user_agent_parsers
+ , os = parsed_yaml.os_parsers
+ , ua_parsers = []
+ , os_parsers = []
+ , parser, i, l;
+
+ // small note about the odd syntax here, we need to JSON.stingify the
+ // regexp so that we are storing it as valid escaped string
+
+ // generate array of user agent parsers
+ for (i = 0, l = agents.length; i < l; i++) {
+ parser = 'r:new RegExp('+ JSON.stringify(agents[i].regex) + ')';
+
+ // check for potential family replacements
+ if (agents[i].family_replacement)
+ parser += ', family:"' + agents[i].family_replacement + '"';
+
+ if (agents[i].major_version_replacement)
+ parser += ', major: "' + agents[i].major_version_replacement + '"';
+
+ ua_parsers.push('{' + parser + '}');
+ }
+
+ // generate array of os parsers
+ for (i = 0, l = os.length; i < l; i++) {
+ parser = 'r:new RegExp('+ JSON.stringify(os[i].regex) + ')';
+
+ // check for potential os replacements
+ if (os[i].os_replacement)
+ parser += ', os:"' + os[i].os_replacement + '"';
+
+ os_parsers.push('{' + parser + '}');
+ }
+ agents.length = os.length = parsed_yaml = 0;
+
+ // now that we have generated arrays of parsers we are gonna build up the
+ // file and make it happen.
+ var agentjs = 'exports.browser = ['+ ua_parsers.join(',') + '];';
+ agentjs += 'exports.os = [' + os_parsers.join(',') + '];';
+
+ // parse down to javascript, we don't want to eval it because someone might
+ // have been naught with our internet and injected a require() statment in
+ // the codez, paranoid ftw
+ var sandbox = { exports: {} };
+ try { vm.runInNewContext(agentjs, sandbox, 'agentjs.vm') }
+ catch (e) { return fn(e) }
+
+ fn(null, sandbox.exports);
+
+ // awesome, we just compiled our own library, time to write to file but do
+ // note that this should be last, as there couldd be compile errors
+ fs.writeFile(exports.output, agentjs, function (err) {
+ // we really don't care if we could write or not..
+ });
+ });
+};
+
+/**
+ * Constants
+ */
+
+exports.version = '0.0.1';
+exports.remote = 'http://ua-parser.googlecode.com/svn/trunk/resources/user_agent_parser.yaml';
+exports.output = path.join(__dirname, '..', 'lib', 'agents.js');
View
342 lib/useragent.js
@@ -0,0 +1,342 @@
+/**
+ * Library dependencies.
+ */
+
+var agents = require('./agents');
+
+/**
+ * The representation of a parsed user agent.
+ *
+ * @constructor
+ * @param {String} family The name of the browser
+ * @param {String} major Major version of the browser
+ * @param {String} minor Minor version of the browser
+ * @param {String} patch Patch version of the browser
+ * @param {String} os Operating system
+ * @api public
+ */
+
+function Agent (family, major, minor, patch, os) {
+ this.family = family || 'Other';
+ this.major = major || '0';
+ this.minor = minor || '0';
+ this.patch = patch || '0';
+ this.os = os || 'Other';
+}
+
+/**
+ * Generates a string output of the parsed user agent.
+ *
+ * @returns {String}
+ * @api public
+ */
+
+Agent.prototype.toAgent = function toAgent () {
+ var output = this.family
+ , version = this.toVersion();
+
+ if (version) output += ' ' + version;
+ return output;
+};
+
+/**
+ * Generates a string output of the parser user agent and operating system.
+ *
+ * @returns {String} "UserAgent 0.0.0 / OS"
+ * @api public
+ */
+
+Agent.prototype.toString = function toString () {
+ var agent = this.toAgent()
+ , os = this.os !== 'Other' ? this.os : false;
+
+ return agent + (os ? ' / ' + os : '');
+};
+
+/**
+ * Outputs a compiled veersion number of the user agent.
+ *
+ * @returns {String}
+ * @api public
+ */
+
+Agent.prototype.toVersion = function toVersion () {
+ var version = '';
+
+ if (this.major) {
+ version += this.major;
+ if (this.minor) {
+ version += '.' + this.minor;
+ // special case here, the patch can also be Alpha, Beta etc so we need
+ // to check if it's a string or not.
+ if (this.patch) {
+ version += (isNaN(+this.patch) ? ' ' : '.') + this.patch;
+ }
+ }
+ }
+
+ return version;
+};
+
+/**
+ * Outputs a JSON string of the Agent
+ *
+ * @returns {String}
+ * @api public
+ */
+
+Agent.prototype.toJSON = function toJSON () {
+ return JSON.stringify({
+ family: this.family
+ , major: this.major
+ , minor: this.minor
+ , patch: this.patch
+ , os: this.os
+ });
+};
+
+/**
+ * Parses the user agent string with the generated parsers from the
+ * ua-parser project on google code.
+ *
+ * @param {String} useragent The user agent string
+ * @param {String} jsagent Optional UA from js to detect chrome frame
+ * @returns {Agent}
+ * @api public
+ */
+
+function parse (useragent, jsagent) {
+ var ua, os, i
+ , browsers = parse.browsers
+ , oss = parse.os
+ , ualength = parse.ua_length
+ , oslength = parse.os_length;
+
+ // find the user agent
+ for (i = 0; i < ualength; i++) {
+ if (ua = useragent.match(browsers[i].r)) {
+ var browser = browsers[i];
+ if (browser.family) ua[1] = browser.family.replace('$1', ua[1]);
+ if (browser.major) ua[2] = browser.major;
+
+ break;
+ }
+ }
+
+ // find the os
+ for (i = 0; i < oslength; i++) {
+ if (os = useragent.match(oss[i].r)) {
+ os[1] = oss[i].os || os[1];
+ break;
+ }
+ }
+
+ ua = ua || [];
+ os = os || [];
+
+ // detect chrome frame, but make sure it's enabled! So we need to check for
+ // the Chrome/ so we know that it's actually using Chrome under the hood
+ if (jsagent &&
+ (jsagent.indexOf('Chrome/') !== -1 && useragent.indexOf('chromeframe') !== -1)
+ ) {
+ ua[1] = 'Chrome Frame (' + ua[1] + ' ' + ua[2] + ')';
+ parser = parse(jsagent);
+
+ // update to the new correct version numbers of chrome frame
+ ua[2] = parser.major;
+ ua[3] = parser.minor;
+ ua[4] = parser.patch;
+ }
+
+ return new Agent(ua[1], ua[2], ua[3], ua[4], os[1]);
+}
+
+/**
+ * Quick lookup references, object lookups are faster than global lookup
+ */
+
+parse.browsers = agents.browser;
+parse.os = agents.os;
+parse.ua_length = parse.browsers.length;
+parse.os_length = parse.os.length;
+
+/**
+ * Small nifty thick that allows us to download a fresh set regexs from t3h
+ * Int3rNetz when we want to. We will be using the compiled version by default
+ * but users can opt-in for updates.
+ *
+ * @param {Boolean} refresh Refresh the dataset from the remote
+ * @api public
+ */
+
+function updater (refresh) {
+ // check if we need to refresh the code from the live servers this does not
+ // impact the rest of the library, it will just update the `agents` code.
+ if (refresh) {
+ require('./update')(function (err, set) {
+ if (err) return; // use old set on error
+
+ // update our agents and lenghts
+ agents = set;
+
+ // update the references to the browsers
+ browsers = agents.browser;
+ oss = agents.os;
+
+ // count the browsers and operating systems again
+ ua_length = browsers.length;
+ os_length = oss.length;
+ });
+ }
+}
+
+/**
+ * Does a more inaccurate but more common check for useragents identification.
+ * The version detection is from the jQuery.com library and is licensed under
+ * MIT.
+ *
+ * @param {String} useragent The user agent
+ * @returns {Object} matches
+ * @api public
+ */
+
+function is (useragent) {
+ var ua = useragent.toLowerCase()
+ , details = {
+ version: (ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [0, "0"])[1]
+ , webkit: ua.indexOf('webkit') !== -1
+ , opera: ua.indexOf('opera') !== -1
+ };
+
+ // the following checks depend on our findings from above
+ details.ie = ua.indexOf('msie') && !details.opera;
+ details.chrome = ua.indexOf('chrome') !== -1 && details.webkit;
+ details.safari = ua.indexOf('safari') !== -1 && !details.chrome;
+ details.mobile_safari = ua.indexOf('apple') !== -1
+ && ua.indexOf('mobile') !== -1 && details.safari;
+ details.firefox = ua.indexOf('mozilla') !== -1
+ && !(details.webkit || ua.indexOf('compatible') !== -1);
+
+ return details;
+}
+
+/**
+ * Converts the result of Agent.toString() back to a new Agent.
+ *
+ * @param {String} prettyAgent
+ * @returns {Agent}
+ * @api public
+ */
+
+function fromString (prettyAgent) {
+ var parts = prettyAgent.split(' / ')
+ , partition = parts[0].split(' ')
+ , versions = partition[1].split('.');
+
+ // return a new user agent instance
+ return new Agent(partition[0]
+ , versions[0]
+ , versions[1]
+ , partition.length == 3 ? partitition[2] : versions[2]
+ , parts.length == 2 ? parts[1] : null
+ );
+}
+
+/**
+ * Transforms a JSON structure back to a new Agent
+ *
+ * @param {Object} data The JSON output of a Agent.toJSON()
+ * @returns {Agent}
+ * @api public
+ */
+
+function fromJSON (data) {
+ return new Agent(data.family, data.major, data.minor, data.patch, data.os);
+}
+
+/**
+ * If you are doing a lot of lookups you might want to cache the results of the
+ * parsed user agent string instead, in memory.
+ *
+ * It takes the same arguments as the regular parse function.
+ * @api public
+ */
+
+function dictionary (ua, jsua) {
+ var key = ua + jsua
+ , match = dictionary.lookup[key];
+
+ if (match) return match;
+ return (dictionary.lookup[key] = parse(ua, jsua))
+}
+
+/**
+ * The dictionary for lookups
+ *
+ * @type {Object}
+ * @api private
+ */
+
+dictionary.lookup = {};
+
+/**
+ * Library version.
+ */
+
+updater.version = '1.0.0';
+
+/**
+ * Expose the Agent constructor.
+ *
+ * @api private
+ */
+
+updater.Agent = Agent;
+
+/**
+ * Expose the useragent parser.
+ *
+ * @api public
+ */
+
+updater.parse = parse;
+
+/**
+ * Expose the dictionary method for faster lookups.
+ *
+ * @api public
+ */
+
+updater.lookup = dictionary;
+
+/**
+ * Expose our agent string parser.
+ *
+ * @api public
+ */
+
+updater.fromString = fromString;
+
+/**
+ * Expose our agent json parser.
+ *
+ * @api public
+ */
+
+updater.fromJSON = fromJSON;
+
+/**
+ * Expose our quick useragent tester.
+ *
+ * @api public
+ */
+
+updater.is = is;
+
+/**
+ * Expose the updater.
+ *
+ * @api public
+ */
+
+module.exports = updater;
View
72 package.json
@@ -1,32 +1,42 @@
{
- "name": "useragent",
- "version": "0.1.2",
- "description": "User-Agent string parser based on Browserscope.org algorithms for more browser reporting",
- "author": "Arnout Kazemier",
- "main": "./useragent.js",
- "keywords":[
- "User-Agent",
- "useragent",
- "browserscope",
- "parser"
- ],
- "maintainers": [
- {
- "name":"Arnout Kazemier",
- "email":"info@3rd-Eden.com",
- "web":"http://www.3rd-Eden.com"
- }
- ],
- "licenses": [
- {
- "type": "Apache",
- "url": "https://github.com/3rd-Eden/node-useragent/blob/master/LICENSE.txt"
- }
- ],
- "repositories": [
- {
- "type": "git",
- "url" : "http://github.com/3rd-Eden/node-useragent.git"
- }
- ]
-}
+ "name": "useragent"
+ , "version": "1.0.0"
+ , "description": "User-Agent string parser based on Browserscope.org algorithms for more browser reporting"
+ , "author": "Arnout Kazemier"
+ , "main": "./index.js"
+ , "keywords":[ "user-agent", "useragent", "browserscope", "ua", "parser", "agent", "user agent"]
+ , "maintainers": [{
+ "name":"Arnout Kazemier"
+ , "email":"info@3rd-Eden.com"
+ , "web":"http://www.3rd-Eden.com"
+ }]
+ , "licenses": [
+ {
+ "type": "Apache-2.0"
+ , "url": "https://github.com/3rd-Eden/useragent/blob/master/LICENSE"
+ }
+ , {
+ "type": "MIT"
+ , "url": "https://github.com/3rd-Eden/useragent/blob/master/LICENSE"
+ }
+ ]
+ , "repository": {
+ "type": "git"
+ , "url" : "http://github.com/3rd-Eden/useragent.git"
+ }
+ , "dependencies": {
+ "yamlparser": "0.0.2"
+ , "request": "2.1.1"
+ , "semver": "1.0.9"
+ }
+ , "devDependencies": {
+ "should": "0.3.1"
+ , "expresso": "0.8.1"
+ , "long-stack-traces": "0.1.2"
+ }
+ , "scripts": {
+ "prepublish": "./bin/update.js"
+ , "postinstall": "./bin/update.js"
+ , "update": "./bin/update.js"
+ }
+}
View
48 tests/browser.js
@@ -1,48 +0,0 @@
-var fs = require('fs'),
- sys = require('sys'),
- useragent = require('../useragent');
-
-fs.readFile( './useragents/user_agent_data.csv', function( err, result ){
-
- if( err )
- return sys.puts( "can't locate file" );
-
- // split file in single rows
- var results = result.toString().split( "\n" ), i = results.length,
- // start times
- total, start = ( new Date() ).valueOf(), failed = [];
-
- while( i-- ){
- (function( ua, res ){
- var agent = useragent.browser( ua.toString().replace( /\"/g, "" ) );
-
- //Check all values are filled in correctly
- for (var key in agent) {
- if (agent[ key ] == undefined) {
- failed[ failed.length ] = Array.prototype.concat.call( arguments, [] ).join(",");
- sys.puts( JSON.stringify( agent ) );
- sys.puts( "Undefined: " + key);
- sys.puts( ua + "\n");
- break;
- }
- }
-
- /*if( !agent || !agent.pretty() || !agent.match ){
- failed[ failed.length ] = Array.prototype.concat.call( arguments, [] ).join(",");
- sys.puts( JSON.stringify( agent ) );
- sys.puts( agent.pretty() )
- sys.puts( ua + "\n");
- }*/
-
- }( results[i].split( "," ) ))
- };
-
- // report
- if( failed.length ){
- sys.puts( "Failed " + failed.length + " out of " + results.length + " tests." );
- } else {
- sys.puts( "\\o/ yay, passed all " + results.length + " tests \\o/" );
- }
-
- sys.puts( "Executed in " + (( new Date()).valueOf() - start) + " ms");
-});
View
19 tests/features.test.js
@@ -0,0 +1,19 @@
+var useragent = require('../')
+ , should = require('should')
+ , features = require('../features');
+
+// use a fixed user agent to ensure proper test results each time
+var ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.24 Safari/535.2";
+
+// make sure we have proper stack traces for when things fail
+require('long-stack-traces');
+
+// the actual tests
+module.exports = {
+ 'satisfy that range selector': function () {
+ var agent = useragent.parse(ua);
+
+ agent.satisfies('15.x || >=19.5.0 || 25.0.0 - 17.2.3').should.be_true;
+ agent.satisfies('>16.12.0').should.be_false;
+ }
+};
View
1,386 tests/fixtures/firefoxes.yaml
@@ -0,0 +1,1386 @@
+# http://people.mozilla.com/~dwitte/ua.txt
+#
+# The following are some example User Agent strings for various versions of
+# Firefox and other Gecko-based browsers. Many of these have been modified by
+# extensions, external applications, distributions, and users. The version number
+# for Firefox 5.0 (and the corresponding Gecko version) is tentative. See
+# https://developer.mozilla.org/En/Gecko_User_Agent_String_Reference for more
+# details. Credit to http://useragentstring.com/ for providing some of these
+# strings.
+
+test_cases:
+ # Firefox 4.0 (prerelease)
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.0; rv:2.0b6pre) Gecko/20100907 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b6pre) Gecko/20100907 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (X11; Linux i686; rv:2.0b6pre) Gecko/20100907 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+
+ # Firefox 4.0.1
+ - user_agent_string: Mozilla/5.0 (Windows NT 5.2; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (WindowsCE 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.5; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux i686 on x86_64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux armv7l; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
+ family: 'Firefox'
+ v1: '4'
+ v2: '0'
+ v3: '1'
+
+ # Firefox 4.0 (prerelease)
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.0; rv:2.0b6pre) Gecko/20100907 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b6pre) Gecko/20100907 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+ - user_agent_string: Mozilla/5.0 (X11; Linux i686; rv:2.0b6pre) Gecko/20100907 Firefox/4.0b6pre
+ family: 'Firefox Beta'
+ v1: '4'
+ v2: '0'
+ v3: b6pre
+
+ # Firefox 5.0 (version number tentative -- not yet released)
+ - user_agent_string: Mozilla/5.0 (Windows NT 5.2; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (WindowsCE 6.0; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.5; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux i686; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux x86_64; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux i686 on x86_64; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (X11; Linux armv7l; rv:2.1.1) Gecko/ Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+
+ # Firefox 3.6
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100804 Gentoo Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 SUSE/3.6.8-0.1.1 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.2.8) Gecko/20100722 Ubuntu/10.04 (lucid) Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; fi-FI; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.8) Gecko/20100727 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.9.2.8) Gecko/20100725 Gentoo Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; FreeBSD i386; de-CH; rv:1.9.2.8) Gecko/20100729 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0C)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.3) Gecko/20121221 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.8) Gecko/20100722 BTRS86393 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0C)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-TW; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; ro; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en; rv:1.9.2.8) Gecko/20100805 Firefox/3.6.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100723 Fedora/3.6.7-1.fc13 Firefox/3.6.7
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100726 CentOS/3.6-3.el5.centos Firefox/3.6.7
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.0
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-PT; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729; .NET4.0E)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.4) Gecko/20100614 Ubuntu/10.04 (lucid) Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.4) Gecko/20100625 Gentoo Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-CA; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100503 Firefox/3.6.4 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; nb-NO; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '4'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.3) Gecko/20100403 Fedora/3.6.3-4.fc13 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100403 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.3) Gecko/20100401 SUSE/3.6.3-1.1 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100404 Ubuntu/10.04 (lucid) Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-GB; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; nl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; fr; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; ca; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:1.9.2.3) Gecko/20100403 Firefox/3.6.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '3'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.7; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2
+ family: 'Firefox'
+ v1: '3'
+ v2: '6'
+ v3: '2'
+
+ # Firefox 3.5
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.9) Gecko/20100402 Ubuntu/9.10 (karmic) Firefox/3.5.9 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.9) Gecko/20100330 Fedora/3.5.9-2.fc12 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9 GTB7.0
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; es-CL; rv:1.9.1.9) Gecko/20100402 Ubuntu/9.10 (karmic) Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; hu-HU; rv:1.9.1.9) Gecko/20100330 Fedora/3.5.9-1.fc12 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100315 Ubuntu/9.10 (karmic) Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.4) Gecko/20091028 Ubuntu/9.10 (karmic) Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; tr; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.1
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; et; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; es-ES; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB5 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.0 (.NET CLR 3.0.30618)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; ca; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.0 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '9'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc11 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.8) Gecko/20100318 Gentoo Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; FreeBSD i386; ja-JP; rv:1.9.1.8) Gecko/20100305 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; sl; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 (.NET CLR 3.5.30729) FirePHP/0.4
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB7.0 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '8'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100305 Gentoo Firefox/3.5.7
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.1.7) Gecko/20100106 Ubuntu/9.10 (karmic) Firefox/3.5.7
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.7) Gecko/20091222 SUSE/3.5.7-1.1.1 Firefox/3.5.7
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.2; fr; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.0.04506.648)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; fa; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.7) Gecko/20091221 MRA 5.5 (build 02842) Firefox/3.5.7 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '7'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.6) Gecko/20100117 Gentoo Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.6) Gecko/20091216 Fedora/3.5.6-1.fc11 Firefox/3.5.6 GTB6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.6) Gecko/20091201 SUSE/3.5.6-1.1.1 Firefox/3.5.6 GTB6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.6) Gecko/20100118 Gentoo Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6 GTB6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6 GTB7.0
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091201 SUSE/3.5.6-1.1.1 Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; cs-CZ; rv:1.9.1.6) Gecko/20100107 Fedora/3.5.6-1.fc12 Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (X11; U; Linux i686; ca; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 ( .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; id; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.4 (build 02647) Firefox/3.5.6 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.5 (build 02842) Firefox/3.5.6 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.5 (build 02842) Firefox/3.5.6
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 GTB6 (.NET CLR 3.5.30729) FBSMTWB
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729) FBSMTWB
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '6'
+ - user_agent_string: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; sv-SE; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3
+ family: 'Firefox'
+ v1: '3'
+ v2: '5'
+ v3: '3'
+
+ # Firefox on the next Gecko major release (preliminary)
+ - user_agent_string: Mozilla/5.0 (Windows NT 5.2; rv:2.0.1) Gecko Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko Firefox/5.0.1
+ family: 'Firefox'
+ v1: '5'
+ v2: '0'
+ v3: '1'
+ - user_agent_string: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko Firefox/5.0.1
+ family: 'Firefox'