Skip to content

Commit bdf127c

Browse files
committed
Update interface is backwards compatible.
1 parent e5341b1 commit bdf127c

File tree

5 files changed

+907
-17
lines changed

5 files changed

+907
-17
lines changed

Diff for: bin/update.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/**
66
* Update our definition file.
77
*/
8-
require('../lib/update').update(undefined, function updating(err, data) {
8+
require('../lib/update').update(function updating(err, data) {
99
if (err) {
1010
console.error('Update unsuccessfull due to reasons');
1111
console.log(err.message);

Diff for: index.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,14 @@ Device.prototype.toJSON = function toJSON() {
357357

358358
/**
359359
* Small nifty thick that allows us to download a fresh set regexs from a remote
360-
* source. Package uses the compiled version by default, but allow for updating from
361-
* the default source (no args) or a custom source, via the `remote` parameter.
360+
* source. Package uses the compiled version by default, but allows for updating
361+
* from the default source (no args) or a custom source, via the "remote"
362+
* parameter.
362363
*
363-
* @param {String} remote optionally specify a custom URL for regex sourcing
364+
* @param {String} [remote] optionally specify a custom URL for regex sourcing
364365
* @api public
365366
*/
366-
module.exports = function updater(remote) {
367+
module.exports = function updater(remote) {
367368
try {
368369
require('./lib/update').update(remote, function updating(err, results) {
369370
if (err) {
@@ -415,10 +416,15 @@ function isSafe(userAgent) {
415416
var consecutive = 0
416417
, code = 0;
417418

419+
if (userAgent.length > 1000) return false;
420+
418421
for (var i = 0; i < userAgent.length; i++) {
419422
code = userAgent.charCodeAt(i);
420-
// numbers between 0 and 9, letters between a and z
421-
if ((code >= 48 && code <= 57) || (code >= 97 && code <= 122)) {
423+
if ((code >= 48 && code <= 57) || // numbers
424+
(code >= 65 && code <= 90) || // letters A-Z
425+
(code >= 97 && code <= 122) || // letters a-z
426+
code <= 32 // spaces and control
427+
) {
422428
consecutive++;
423429
} else {
424430
consecutive = 0;
@@ -443,6 +449,10 @@ function isSafe(userAgent) {
443449
* @api public
444450
*/
445451
exports.parse = function parse(userAgent, jsAgent) {
452+
if (userAgent && userAgent.length > 1000) {
453+
userAgent = userAgent.substring(0, 1000);
454+
}
455+
446456
if (!userAgent || !isSafe(userAgent)) return new Agent();
447457

448458
var length = agentparserslength
@@ -506,7 +516,8 @@ exports.parse = function parse(userAgent, jsAgent) {
506516
* @param {String} jsAgent Optional UA from js to detect chrome frame
507517
* @api public
508518
*/
509-
var LRU = require('lru-cache')(5000);
519+
var lruCache = require('lru-cache');
520+
var LRU = new lruCache(5000);
510521
exports.lookup = function lookup(userAgent, jsAgent) {
511522
var key = (userAgent || '')+(jsAgent || '')
512523
, cached = LRU.get(key);

Diff for: lib/update.js

+59-7
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,44 @@ var request = require('request')
1515
, yaml = require('yamlparser');
1616

1717
/**
18-
* Update the regexp.js file
18+
* Local modules.
19+
*/
20+
var old = require('./regexps');
21+
22+
function getKnown(old) {
23+
var known = new Set();
24+
for (var type in old) {
25+
if (!old.hasOwnProperty(type)) continue;
26+
var group = old[type];
27+
for (var i = 0; i < group.length; i++) {
28+
var regex = group[i][0].source;
29+
known.add(regex);
30+
}
31+
}
32+
return known;
33+
}
34+
35+
var known = getKnown(old);
36+
37+
function isSafe(source) {
38+
if (!/[+*{].*[+*{]/.test(source)) return true; // -35%
39+
return false;
40+
}
41+
42+
/**
43+
* Update the regexps.js file
1944
*
20-
* @param {String} remote optional remote URL for regex source
45+
* @param {String} [remote] optional remote URL for regex source
2146
* @param {Function} callback Completion callback.
2247
* @api public
2348
*/
24-
exports.update = function update(remote, callback) {
49+
exports.update = function update() {
50+
let remote, callback;
51+
52+
if (arguments.length === 2) [remote, callback] = arguments;
53+
else if (arguments.length === 1) [callback] = arguments;
2554
if (remote !== undefined) exports.remote = remote;
55+
2656
// Prepend local additions that are missing from the source
2757
fs.readFile(exports.before, 'utf8', function reading(err, before) {
2858
if (err) return callback(err);
@@ -47,10 +77,8 @@ exports.update = function update(remote, callback) {
4777
//
4878
tmp.file(function (err, tempFilePath) {
4979
if (err) return;
50-
5180
fs.writeFile(tempFilePath, source, function idk(err) {
5281
if (err) return
53-
5482
fs.rename(tempFilePath, exports.output, function(err) {
5583

5684
});
@@ -70,6 +98,7 @@ exports.update = function update(remote, callback) {
7098
* @api public
7199
*/
72100
exports.parse = function parse(sources, callback) {
101+
var unsafe = [];
73102
var results = {};
74103

75104
var data = sources.reduce(function parser(memo, data) {
@@ -124,16 +153,29 @@ exports.parse = function parse(sources, callback) {
124153
var resources = data[details.resource]
125154
, name = details.resource.replace('_parsers', '')
126155
, resource
156+
, regex
157+
, source
127158
, parser;
128159

129160
for (var i = 0, l = resources.length; i < l; i++) {
130161
resource = resources[i];
162+
regex = resource.regex;
163+
164+
source = new RegExp(regex).source;
165+
if (!known.has(source)) {
166+
// A quick check, regexes not matching those are clearly safe
167+
// This check excludes about 35% of all regexps we have
168+
if (!isSafe(source)) {
169+
unsafe.push(source);
170+
}
171+
known.add(source);
172+
}
131173

132174
// We need to JSON stringify the data to properly add slashes escape other
133175
// kinds of crap in the RegularExpression. If we don't do thing we get
134176
// some illegal token warnings.
135177
parser = 'parser = Object.create(null);\n';
136-
parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + ');\n';
178+
parser += 'parser[0] = new RegExp('+ JSON.stringify(regex) + ');\n';
137179

138180
// Check if we have replacement for the parsed family name
139181
if (resource[details.replacement]) {
@@ -172,6 +214,16 @@ exports.parse = function parse(sources, callback) {
172214
}
173215
});
174216

217+
if (unsafe.length > 0) {
218+
console.log('There are new regexps! Here they are, one per line:');
219+
for (var i = 0; i < unsafe.length; i++) {
220+
console.log(' ' + unsafe[i]);
221+
}
222+
console.log('Those might be potentially unsafe and cause ReDoS.');
223+
console.log('Make sure to take them through Weideman\'s tool and to inspect them!');
224+
console.log('See https://github.com/NicolaasWeideman/RegexStaticAnalysis');
225+
}
226+
175227
// Generate a correct format
176228
exports.generate(results, callback);
177229
};
@@ -222,7 +274,7 @@ exports.generate = function generate(results, callback) {
222274
* @type {String}
223275
* @api private
224276
*/
225-
exports.remote = 'https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml';
277+
exports.remote = process.env.USERAGENT_REMOTE ? process.env.USERAGENT_REMOTE : 'https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml';
226278

227279
/**
228280
* The locations of our local regexes yaml files.

0 commit comments

Comments
 (0)