Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't replace regex / function placeholders within string literals #79

Merged
merged 2 commits into from May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 22 additions & 3 deletions index.js
Expand Up @@ -6,9 +6,12 @@ See the accompanying LICENSE file for terms.

'use strict';

var randomBytes = require('randombytes');

// Generate an internal UID to make the regexp pattern harder to guess.
var UID = Math.floor(Math.random() * 0x10000000000).toString(16);
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D|M|S|U|I)-' + UID + '-(\\d+)__@"', 'g');
var UID_LENGTH = 16;
var UID = generateUID();
var PLACE_HOLDER_REGEXP = new RegExp('(\\\\)?"@__(F|R|D|M|S|U|I)-' + UID + '-(\\d+)__@"', 'g');

var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
var IS_PURE_FUNCTION = /function.*?\(/;
Expand All @@ -31,6 +34,15 @@ function escapeUnsafeChars(unsafeChar) {
return ESCAPED_CHARS[unsafeChar];
}

function generateUID() {
var bytes = randomBytes(UID_LENGTH);
var result = '';
for(var i=0; i<UID_LENGTH; ++i) {
result += bytes[i].toString(16);
}
return result;
}

function deleteFunctions(obj){
var functionKeys = [];
for (var key in obj) {
Expand Down Expand Up @@ -187,7 +199,14 @@ module.exports = function serialize(obj, options) {
// Replaces all occurrences of function, regexp, date, map and set placeholders in the
// JSON string with their string representations. If the original value can
// not be found, then `undefined` is used.
return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) {
return str.replace(PLACE_HOLDER_REGEXP, function (match, backSlash, type, valueIndex) {
// The placeholder may not be preceded by a backslash. This is to prevent
// replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting
// invalid JS.
if (backSlash) {
return match;
}

if (type === 'D') {
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
}
Expand Down
11 changes: 9 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -29,5 +29,8 @@
"chai": "^4.1.0",
"mocha": "^7.0.0",
"nyc": "^15.0.0"
},
"dependencies": {
"randombytes": "^2.1.0"
}
}
27 changes: 27 additions & 0 deletions test/unit/serialize.js
@@ -1,9 +1,23 @@
/* global describe, it, beforeEach */
'use strict';

// temporarily monkeypatch `crypto.randomBytes` so we'll have a
// predictable UID for our tests
var crypto = require('crypto');
var oldRandom = crypto.randomBytes;
crypto.randomBytes = function(len, cb) {
var buf = Buffer.alloc(len);
buf.fill(0x00);
if (cb)
cb(null, buf);
return buf;
};

var serialize = require('../../'),
expect = require('chai').expect;

crypto.randomBytes = oldRandom;

describe('serialize( obj )', function () {
it('should be a function', function () {
expect(serialize).to.be.a('function');
Expand Down Expand Up @@ -493,4 +507,17 @@ describe('serialize( obj )', function () {
expect(serialize([1], 2)).to.equal('[\n 1\n]');
});
});

describe('placeholders', function() {
it('should not be replaced within string literals', function () {
// Since we made the UID deterministic this should always be the placeholder
var fakePlaceholder = '"@__R-0000000000000000-0__@';
var serialized = serialize({bar: /1/i, foo: fakePlaceholder}, {uid: 'foo'});
var obj = eval('(' + serialized + ')');
expect(obj).to.be.a('Object');
expect(obj.foo).to.be.a('String');
expect(obj.foo).to.equal(fakePlaceholder);
});
});

});