优化
This commit is contained in:
88
client/test/unit/rendering-safety.test.js
Normal file
88
client/test/unit/rendering-safety.test.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { createMessageElement } from '../../public/call/chat/renderer-chat.js';
|
||||
import { createParticipantTile, getParticipantTile } from '../../public/call/participants/renderer-participant-grid.js';
|
||||
import { createUserEntryElement } from '../../public/call/renderers/renderer-ui.js';
|
||||
import { renderOnlineUsers } from '../../public/call/signaling/connect-directory.js';
|
||||
|
||||
const formatTimestamp = value => value;
|
||||
const unsafeText = '<img src=x onerror=alert(1)>Alice';
|
||||
|
||||
function mediaState(overrides = {}) {
|
||||
return {
|
||||
audio: true,
|
||||
video: true,
|
||||
isSpeaking: false,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe('safe dynamic rendering', () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('renders chat text as text, not markup', () => {
|
||||
const element = createMessageElement({
|
||||
id: 'msg-1',
|
||||
type: 'text',
|
||||
isSelf: false,
|
||||
senderName: unsafeText,
|
||||
senderAvatar: '/images/p1.png',
|
||||
content: unsafeText,
|
||||
timestamp: 'now'
|
||||
}, formatTimestamp);
|
||||
|
||||
expect(element.querySelector('.message-text').textContent).toBe(unsafeText);
|
||||
expect(element.querySelector('.message-content img')).toBeNull();
|
||||
expect(element.querySelector('.message-sender').textContent).toBe(unsafeText);
|
||||
});
|
||||
|
||||
test('renders participant names safely and finds ids without selector injection', () => {
|
||||
const participantId = 'room"] [data-bad="1';
|
||||
const tile = createParticipantTile(participantId, unsafeText);
|
||||
const grid = document.createElement('div');
|
||||
grid.appendChild(tile);
|
||||
|
||||
expect(tile.querySelector('.absolute.bottom-3 span').textContent).toBe(unsafeText);
|
||||
expect(tile.querySelector('.absolute.bottom-3 img')).toBeNull();
|
||||
expect(getParticipantTile(grid, participantId)).toBe(tile);
|
||||
});
|
||||
|
||||
test('renders user list entries without interpreting user profile fields as HTML', () => {
|
||||
const entry = createUserEntryElement({
|
||||
role: 'participant',
|
||||
id: 'participant-1',
|
||||
user: {
|
||||
name: unsafeText,
|
||||
avatar: '/images/p2.png',
|
||||
mediaState: mediaState({ audio: false })
|
||||
}
|
||||
});
|
||||
|
||||
expect(entry.textContent).toContain(unsafeText);
|
||||
expect(entry.querySelectorAll('img')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('renders online users without injecting markup from directory data', () => {
|
||||
const onlineUsersList = document.createElement('div');
|
||||
const usersContainer = document.createElement('div');
|
||||
const onlineUsersSummary = document.createElement('div');
|
||||
|
||||
renderOnlineUsers({
|
||||
users: [{
|
||||
name: unsafeText,
|
||||
userId: unsafeText,
|
||||
avatar: '/images/p1.png',
|
||||
role: 'participant',
|
||||
connectionId: 'room-1'
|
||||
}],
|
||||
currentUserId: 'other-user',
|
||||
onlineUsersList,
|
||||
usersContainer,
|
||||
onlineUsersSummary
|
||||
});
|
||||
|
||||
expect(usersContainer.textContent).toContain(unsafeText);
|
||||
expect(usersContainer.querySelector('button')).toBeNull();
|
||||
expect(usersContainer.querySelectorAll('img')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user