Skip to content

Commit

Permalink
misc(proto): ensure all strings are well-formed utf
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Apr 2, 2024
1 parent 4f54d5a commit c3fcd43
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 9 deletions.
32 changes: 24 additions & 8 deletions core/lib/proto-preprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,28 +83,44 @@ function processForProto(lhr) {
}

/**
* Remove any found empty strings, as they are dropped after round-tripping anyway
* Execute `cb(obj, key)` on every object property where obj[key] is a string, recursively.
* @param {any} obj
* @param {(obj: Record<string, string>, key: string) => void} cb
*/
function removeStrings(obj) {
function iterateStrings(obj, cb) {
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'string' && obj[key] === '') {
delete obj[key];
} else if (typeof obj[key] === 'object' || Array.isArray(obj[key])) {
removeStrings(obj[key]);
if (typeof obj[key] === 'string') {
cb(obj, key);
} else {
iterateStrings(obj[key], cb);
}
});
} else if (Array.isArray(obj)) {
obj.forEach(item => {
if (typeof item === 'object' || Array.isArray(item)) {
removeStrings(item);
iterateStrings(item, cb);
}
});
}
}

removeStrings(reportJson);
iterateStrings(reportJson, (obj, key) => {
const value = obj[key];

// Remove empty strings, as they are dropped after round-tripping anyway.
if (value === '') {
delete obj[key];
return;
}

// Sanitize lone surrogates.
// @ts-expect-error node 20
if (String.prototype.isWellFormed && !value.isWellFormed()) {
// @ts-expect-error node 20
obj[key] = value.toWellFormed();
}
});

return reportJson;
}
Expand Down
22 changes: 22 additions & 0 deletions core/test/lib/proto-preprocessor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,28 @@ Object {

expect(output).toMatchObject(expectation);
});

it('sanitizes lone surrogates', () => {
// Don't care about Node 18 here. We just need this to work in Chrome, and it does.
if (!String.prototype.toWellFormed) {
return;
}

const input = {
'audits': {
'critical-request-chains': {
'details': {
'chains': {
'1': 'hello \uD83E',
},
},
},
},
};
const output = processForProto(input);

expect(output.audits['critical-request-chains'].details.chains[1]).toEqual('hello �');
});
});

describeIfProtoExists('round trip JSON comparison subsets', () => {
Expand Down
2 changes: 1 addition & 1 deletion shared/test/util-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ describe('util helpers', () => {
});

describe('truncate', () => {
it('truncates based on visual characters', () => {
it.only('truncates based on visual characters', () => {
expect(Util.truncate('aaa', 30)).toEqual('aaa');
expect(Util.truncate('aaa', 3)).toEqual('aaa');
expect(Util.truncate('aaa', 2)).toEqual('a…');
Expand Down

0 comments on commit c3fcd43

Please sign in to comment.