Skip to content

Commit 06b6a47

Browse files
committed
Ensure backslashes are properly escaped in sarif messages
Problem was that we were not globally replaceing `\\` with `\`. Also, this PR adds some new tests to sarif-utils.ts. In doing so, we have fixed a small bug in getPathRelativeToSourceLocationPrefix. Previously, we were uri decoding the sarifRelativeUri. However, this is no longer correct because the result is another URI and it should remain encoded if it originally was. Resolves #585
1 parent 7ca456d commit 06b6a47

3 files changed

Lines changed: 137 additions & 49 deletions

File tree

extensions/ql-vscode/src/sarif-utils.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ interface NoLocation {
1515
}
1616

1717
type ParsedSarifLocation =
18-
| (ResolvableLocationValue & { userVisibleFile: string })
18+
| (ResolvableLocationValue & {
19+
20+
userVisibleFile: string;
21+
})
1922
// Resolvable locations have a `uri` field, but it will sometimes include
2023
// a source location prefix, which contains build-specific information the user
2124
// doesn't really need to see. We ensure that `userVisibleFile` will not contain
@@ -28,7 +31,10 @@ export type SarifMessageComponent = string | SarifLink
2831
* Unescape "[", "]" and "\\" like in sarif plain text messages
2932
*/
3033
export function unescapeSarifText(message: string): string {
31-
return message.replace(/\\\[/g, '[').replace(/\\\]/g, ']').replace(/\\\\/, '\\');
34+
return message
35+
.replace(/\\\[/g, '[')
36+
.replace(/\\\]/g, ']')
37+
.replace(/\\\\/g, '\\');
3238
}
3339

3440
export function parseSarifPlainTextMessage(message: string): SarifMessageComponent[] {
@@ -59,17 +65,18 @@ export function parseSarifPlainTextMessage(message: string): SarifMessageCompone
5965
* @param sourceLocationPrefix The source location prefix of a database. May be
6066
* unix style `/foo/bar/baz` or windows-style `C:\foo\bar\baz`.
6167
* @param sarifRelativeUri A uri relative to sourceLocationPrefix.
62-
* @returns A string that is valid for the `.file` field of a `FivePartLocation`:
68+
*
69+
* @returns A URI string that is valid for the `.file` field of a `FivePartLocation`:
6370
* directory separators are normalized, but drive letters `C:` may appear.
6471
*/
6572
export function getPathRelativeToSourceLocationPrefix(
6673
sourceLocationPrefix: string,
67-
sarifRelativeUui: string
74+
sarifRelativeUri: string
6875
) {
6976
const normalizedSourceLocationPrefix = sourceLocationPrefix.replace(/\\/g, '/');
7077
return `file:${
71-
path.join(normalizedSourceLocationPrefix, decodeURIComponent(sarifRelativeUui))
72-
}`;
78+
path.join(normalizedSourceLocationPrefix, sarifRelativeUri)
79+
}`;
7380
}
7481

7582
export function parseSarifLocation(
@@ -89,12 +96,13 @@ export function parseSarifLocation(
8996
const uri = physicalLocation.artifactLocation.uri;
9097

9198
const fileUriRegex = /^file:/;
92-
const effectiveLocation = uri.match(fileUriRegex)
99+
const hasFilePrefix = uri.match(fileUriRegex);
100+
const effectiveLocation = hasFilePrefix
93101
? uri
94102
: getPathRelativeToSourceLocationPrefix(sourceLocationPrefix, uri);
95-
const userVisibleFile = uri.match(fileUriRegex)
96-
? decodeURIComponent(uri.replace(fileUriRegex, ''))
97-
: uri;
103+
const userVisibleFile = decodeURIComponent(hasFilePrefix
104+
? uri.replace(fileUriRegex, '')
105+
: uri);
98106

99107
if (physicalLocation.region === undefined) {
100108
// If the region property is absent, the physicalLocation object refers to the entire file.

extensions/ql-vscode/src/vscode-tests/no-workspace/sarifMessageParsingTest.test.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import 'mocha';
2+
import { expect } from 'chai';
3+
import * as Sarif from 'sarif';
4+
5+
import { getPathRelativeToSourceLocationPrefix, parseSarifLocation, parseSarifPlainTextMessage, unescapeSarifText } from '../../src/sarif-utils';
6+
7+
8+
describe('parsing sarif', () => {
9+
it('should be able to parse a simple message from the spec', async function() {
10+
const message = 'Tainted data was used. The data came from [here](3).';
11+
const results = parseSarifPlainTextMessage(message);
12+
expect(results).to.deep.equal([
13+
'Tainted data was used. The data came from ',
14+
{ dest: 3, text: 'here' }, '.'
15+
]);
16+
});
17+
18+
it('should be able to parse a complex message from the spec', async function() {
19+
const message = 'Prohibited term used in [para\\[0\\]\\\\spans\\[2\\]](1).';
20+
const results = parseSarifPlainTextMessage(message);
21+
expect(results).to.deep.equal([
22+
'Prohibited term used in ',
23+
{ dest: 1, text: 'para[0]\\spans[2]' }, '.'
24+
]);
25+
});
26+
it('should be able to parse a broken complex message from the spec', async function() {
27+
const message = 'Prohibited term used in [para\\[0\\]\\\\spans\\[2\\](1).';
28+
const results = parseSarifPlainTextMessage(message);
29+
expect(results).to.deep.equal([
30+
'Prohibited term used in [para[0]\\spans[2](1).'
31+
]);
32+
});
33+
it('should be able to parse a message with extra escaping the spec', async function() {
34+
const message = 'Tainted data was used. The data came from \\[here](3).';
35+
const results = parseSarifPlainTextMessage(message);
36+
expect(results).to.deep.equal([
37+
'Tainted data was used. The data came from [here](3).'
38+
]);
39+
});
40+
41+
it('should unescape sarif text', () => {
42+
expect(unescapeSarifText('\\\\ \\\\ \\[ \\[ \\] \\]')).to.eq('\\ \\ [ [ ] ]');
43+
// Also show that unescaped special chars are unchanged...is this correct?
44+
expect(unescapeSarifText('\\ \\ [ [ ] ]')).to.eq('\\ \\ [ [ ] ]');
45+
});
46+
47+
it('should normalize source locations', () => {
48+
expect(getPathRelativeToSourceLocationPrefix('C:\\a\\b', '?x=test'))
49+
.to.eq('file:C:/a/b/?x=test');
50+
expect(getPathRelativeToSourceLocationPrefix('C:\\a\\b', '%3Fx%3Dtest'))
51+
.to.eq('file:C:/a/b/%3Fx%3Dtest');
52+
});
53+
54+
describe('parseSarifLocation', () => {
55+
it('should parse a sarif location with "no location"', () => {
56+
expect(parseSarifLocation({ }, '')).to.deep.equal({
57+
hint: 'no physical location'
58+
});
59+
expect(parseSarifLocation({ physicalLocation: {} }, '')).to.deep.equal({
60+
hint: 'no artifact location'
61+
});
62+
expect(parseSarifLocation({ physicalLocation: { artifactLocation: { } } }, '')).to.deep.equal({
63+
hint: 'artifact location has no uri'
64+
});
65+
});
66+
67+
it('should parse a sarif location with no region and no file protocol', () => {
68+
const location: Sarif.Location = {
69+
physicalLocation: {
70+
artifactLocation: {
71+
uri: 'abc?x=test'
72+
}
73+
}
74+
};
75+
expect(parseSarifLocation(location, 'prefix')).to.deep.equal({
76+
uri: 'file:prefix/abc?x=test',
77+
userVisibleFile: 'abc?x=test'
78+
});
79+
});
80+
81+
it('should parse a sarif location with no region and file protocol', () => {
82+
const location: Sarif.Location = {
83+
physicalLocation: {
84+
artifactLocation: {
85+
uri: 'file:abc%3Fx%3Dtest'
86+
}
87+
}
88+
};
89+
expect(parseSarifLocation(location, 'prefix')).to.deep.equal({
90+
uri: 'file:abc%3Fx%3Dtest',
91+
userVisibleFile: 'abc?x=test'
92+
});
93+
});
94+
95+
it('should parse a sarif location with a region and file protocol', () => {
96+
const location: Sarif.Location = {
97+
physicalLocation: {
98+
artifactLocation: {
99+
uri: 'file:abc%3Fx%3Dtest'
100+
},
101+
region: {
102+
startLine: 1,
103+
startColumn: 2,
104+
endLine: 3,
105+
endColumn: 4
106+
}
107+
}
108+
};
109+
expect(parseSarifLocation(location, 'prefix')).to.deep.equal({
110+
uri: 'file:abc%3Fx%3Dtest',
111+
userVisibleFile: 'abc?x=test',
112+
startLine: 1,
113+
startColumn: 2,
114+
endLine: 3,
115+
endColumn: 3
116+
});
117+
});
118+
});
119+
});

0 commit comments

Comments
 (0)