Skip to content

Commit 13f4921

Browse files
committed
Add tests for custom remote provider avatar URLs
Covers token interpolation, email parsing edge cases, and URI encoding to ensure special characters cannot break URL structure or enable injection. (#5155)
1 parent 58084d9 commit 13f4921

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/* eslint-disable no-template-curly-in-string */
2+
import * as assert from 'assert';
3+
import type { RemotesUrlsConfig } from '../../models/remoteProvider.js';
4+
import { CustomRemoteProvider } from '../custom.js';
5+
6+
const stubUrls: RemotesUrlsConfig = {
7+
repository: '',
8+
branches: '',
9+
branch: '',
10+
commit: '',
11+
file: '',
12+
fileInBranch: '',
13+
fileInCommit: '',
14+
fileLine: '',
15+
fileRange: '',
16+
};
17+
18+
function createProvider(avatarTemplate: string): CustomRemoteProvider {
19+
return new CustomRemoteProvider('git.corp.com', 'org/repo', { ...stubUrls, avatar: avatarTemplate });
20+
}
21+
22+
suite('CustomRemoteProvider.getUrlForAvatar', () => {
23+
suite('basic interpolation', () => {
24+
test('interpolates all tokens', () => {
25+
const provider = createProvider('https://avatars.corp.com/${email}/${emailName}/${domain}?s=${size}');
26+
const url = provider.getUrlForAvatar('alice@corp.com', 32);
27+
assert.strictEqual(url, 'https://avatars.corp.com/alice%40corp.com/alice/corp.com?s=32');
28+
});
29+
30+
test('returns undefined when no avatar template is configured', () => {
31+
const provider = new CustomRemoteProvider('git.corp.com', 'org/repo', stubUrls);
32+
assert.strictEqual(provider.getUrlForAvatar('alice@corp.com', 32), undefined);
33+
assert.strictEqual(provider.avatarUrlTemplate, undefined);
34+
});
35+
36+
test('handles email-only template', () => {
37+
const provider = createProvider('https://avatars.corp.com/${email}');
38+
assert.strictEqual(
39+
provider.getUrlForAvatar('alice@corp.com', 16),
40+
'https://avatars.corp.com/alice%40corp.com',
41+
);
42+
});
43+
44+
test('handles size-only template', () => {
45+
const provider = createProvider('https://avatars.corp.com/default?s=${size}');
46+
assert.strictEqual(provider.getUrlForAvatar('alice@corp.com', 64), 'https://avatars.corp.com/default?s=64');
47+
});
48+
});
49+
50+
suite('email parsing', () => {
51+
test('splits on last @ for standard email', () => {
52+
const provider = createProvider('${emailName}---${domain}');
53+
assert.strictEqual(provider.getUrlForAvatar('alice@corp.com', 16), 'alice---corp.com');
54+
});
55+
56+
test('preserves local-part containing @ (RFC 5322)', () => {
57+
const provider = createProvider('${emailName}---${domain}');
58+
assert.strictEqual(
59+
provider.getUrlForAvatar('"user@internal"@corp.com', 16),
60+
'%22user%40internal%22---corp.com',
61+
);
62+
});
63+
64+
test('handles email with no @', () => {
65+
const provider = createProvider('${emailName}---${domain}');
66+
assert.strictEqual(provider.getUrlForAvatar('localonly', 16), 'localonly---');
67+
});
68+
69+
test('handles email with multiple @ signs', () => {
70+
const provider = createProvider('${emailName}---${domain}');
71+
assert.strictEqual(provider.getUrlForAvatar('a@b@c.com', 16), 'a%40b---c.com');
72+
});
73+
});
74+
75+
suite('URI encoding of special characters', () => {
76+
test('encodes slash in email', () => {
77+
const provider = createProvider('https://avatars.corp.com/${email}');
78+
const url = provider.getUrlForAvatar('user/admin@corp.com', 16);
79+
assert.ok(url!.includes('user%2Fadmin%40corp.com'));
80+
});
81+
82+
test('encodes question mark in email', () => {
83+
const provider = createProvider('https://avatars.corp.com/${email}');
84+
const url = provider.getUrlForAvatar('user?q=1@corp.com', 16);
85+
assert.ok(url!.includes('user%3Fq%3D1%40corp.com'));
86+
});
87+
88+
test('encodes hash in email', () => {
89+
const provider = createProvider('https://avatars.corp.com/${email}');
90+
const url = provider.getUrlForAvatar('user#tag@corp.com', 16);
91+
assert.ok(url!.includes('user%23tag%40corp.com'));
92+
});
93+
94+
test('encodes spaces', () => {
95+
const provider = createProvider('https://avatars.corp.com/${emailName}');
96+
const url = provider.getUrlForAvatar('first last@corp.com', 16);
97+
assert.ok(url!.includes('first%20last'));
98+
});
99+
100+
test('encodes unicode characters', () => {
101+
const provider = createProvider('https://avatars.corp.com/${emailName}');
102+
const url = provider.getUrlForAvatar('ünïcödé@corp.com', 16);
103+
assert.strictEqual(url, 'https://avatars.corp.com/%C3%BCn%C3%AFc%C3%B6d%C3%A9');
104+
});
105+
106+
test('encodes colon and at-sign to prevent authority injection', () => {
107+
const provider = createProvider('https://avatars.corp.com/${email}');
108+
const url = provider.getUrlForAvatar('evil:pass@attacker.com', 16);
109+
assert.ok(url!.includes('evil%3Apass%40attacker.com'));
110+
});
111+
});
112+
});

0 commit comments

Comments
 (0)