Skip to content

Commit 3694e85

Browse files
【fix】UT, 播放控件字符转义
AI-GEN: 70% Cursor
1 parent 54bd20f commit 3694e85

3 files changed

Lines changed: 33 additions & 32 deletions

File tree

‎src/mapboxgl/control/VideoPlayControl.js‎

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,23 @@ const PAUSE_ICON =
1010
'<svg viewBox="0 0 24 24" class="sm-mapboxgl-play-svg sm-mapboxgl-play-svg--pause" aria-hidden="true"><path d="M6 5h4v14H6V5zm8 0h4v14h-4V5z"/></svg>';
1111
const DEFAULT_CONTENT = `<button type="button" class="sm-mapboxgl-play-btn">${PLAY_ICON}${PAUSE_ICON}</button>`;
1212

13-
export function escapeHTML(strings) {
14-
var result = '';
15-
for (var i = 0; i < strings.length; i++) {
16-
result += strings[i];
17-
if (i + 1 < arguments.length) {
18-
var value = arguments[i + 1] || '';
19-
result += String(value).replace(/[&<>"'/]/g, function (s) {
20-
return {
21-
'&': '&amp;',
22-
'<': '&lt;',
23-
'>': '&gt;',
24-
'"': '&quot;',
25-
"'": '&apos;',
26-
'/': '&#x2F;'
27-
}[s];
28-
});
29-
}
30-
}
31-
return result;
13+
// 保留合法 HTML 结构,只移除危险内容。移除 script、iframe、onerror、onclick、javascript: 等危险标签和属性。
14+
function sanitizeHTML(html) {
15+
var template = document.createElement('template');
16+
template.innerHTML = String(html);
17+
template.content.querySelectorAll('script, iframe, object, embed, style, link, meta').forEach(function (element) {
18+
element.remove();
19+
});
20+
template.content.querySelectorAll('*').forEach(function (element) {
21+
Array.from(element.attributes).forEach(function (attribute) {
22+
var name = attribute.name.toLowerCase();
23+
var value = attribute.value.trim().toLowerCase();
24+
if (name.indexOf('on') === 0 || /^(javascript|vbscript|data):/.test(value)) {
25+
element.removeAttribute(attribute.name);
26+
}
27+
});
28+
});
29+
return template.innerHTML;
3230
}
3331

3432
/**
@@ -70,7 +68,7 @@ export class VideoPlayControl {
7068
* @returns {VideoPlayControl} this。
7169
*/
7270
setHTML(html) {
73-
this._content = escapeHTML`${String(html)}`;
71+
this._content = sanitizeHTML(html);
7472
this._renderContent();
7573
return this;
7674
}

‎test/mapboxgl/control/VideoPlayControlLayerSpec.js‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,17 @@ describe('mapboxgl_VideoPlayControl with VideoLayer', () => {
9696
expect(nextLayer.listeners.pause).toBeUndefined();
9797
});
9898

99-
it('renders escaped text from setHTML before and after onAdd', () => {
99+
it('renders custom HTML from setHTML before and after onAdd', () => {
100100
var videoLayer = createVideoLayer(true);
101101
var control = new VideoPlayControl({ target: videoLayer });
102102

103103
expect(control.setHTML('<i class="my-play"></i>')).toBe(control);
104104
control.onAdd({});
105-
expect(control._container.textContent).toBe('<i class="my-play"></i>');
106-
expect(control._container.querySelector('.sm-mapboxgl-play-btn')).toBeNull();
105+
expect(control._container.querySelector('.my-play')).not.toBeNull();
107106
expect(control._button).toBeNull();
108107

109108
control.setHTML('<b class="next"></b>');
110-
expect(control._container.textContent).toBe('<b class="next"></b>');
109+
expect(control._container.querySelector('.next')).not.toBeNull();
111110

112111
control.onRemove();
113112
});
@@ -135,12 +134,15 @@ describe('mapboxgl_VideoPlayControl with VideoLayer', () => {
135134
control.onRemove();
136135
});
137136

138-
it('escapes markup passed to setHTML', () => {
137+
it('filters dangerous markup passed to setHTML', () => {
139138
var control = new VideoPlayControl();
140139
control.onAdd({});
141-
control.setHTML('<script>window.__xss = 1</script><img onerror="alert(1)">');
140+
control.setHTML('<script>window.__xss = 1</script><img onerror="alert(1)" src="javascript:alert(1)"><b class="safe">safe</b>');
142141

143-
expect(control._container.textContent).toBe('<script>window.__xss = 1</script><img onerror="alert(1)">');
142+
expect(control._container.querySelector('script')).toBeNull();
143+
expect(control._container.querySelector('img').hasAttribute('onerror')).toBeFalse();
144+
expect(control._container.querySelector('img').hasAttribute('src')).toBeFalse();
145+
expect(control._container.querySelector('.safe')).not.toBeNull();
144146
expect(window.__xss).toBeUndefined();
145147

146148
control.onRemove();
@@ -153,7 +155,7 @@ describe('mapboxgl_VideoPlayControl with VideoLayer', () => {
153155
control.onRemove();
154156
control.onAdd({});
155157

156-
expect(control._container.textContent).toBe('<i class="again"></i>');
158+
expect(control._container.querySelector('.again')).not.toBeNull();
157159

158160
control.onRemove();
159161
});

‎test/mapboxgl/mapping/VideoMapSpec.js‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,20 @@ describe('mapboxgl_videoMap', () => {
108108
spyOn(videoMap.coordTransfer, 'toSpatialCoordinate').and.returnValue([13358338.895192828, 3503549.8435043753, 0]);
109109
var projectedPoint = { x: 10, y: 20 };
110110
spyOn(videoMap.map, 'project').and.returnValue(projectedPoint);
111+
spyOn(videoMap.coordTransfer, 'toVideoCoordinate').and.returnValue({ data64F: [50, 50] });
111112
videoMap.map.fire('click', { lngLat: { lng: 120, lat: 30 }, point: { x: 10, y: 20 } });
112113
expect(clickEvent.lngLat.lng).toBeCloseTo(120, 6);
113114
expect(clickEvent.lngLat.lat).toBeCloseTo(30, 6);
114-
expect(clickEvent.point).toBe(projectedPoint);
115+
expect(clickEvent.point).toEqual(projectedPoint);
115116
expect(clickEvent.mapEvent.lngLat).toEqual({ lng: 120, lat: 30 });
116117
var container = {};
117118
spyOn(videoMap.map, 'getContainer').and.returnValue(container);
118-
spyOn(videoMap.coordTransfer, 'toVideoCoordinate').and.returnValue({ data64F: [50, 50] });
119119
expect(videoMap.getContainer()).toBe(container);
120-
expect(videoMap.project({ lng: 120, lat: 30 })).toBe(projectedPoint);
120+
expect(videoMap.project({ lng: 120, lat: 30 })).toEqual(projectedPoint);
121121
expect(videoMap.map.project).toHaveBeenCalledWith(jasmine.objectContaining({ lng: 50, lat: 50 }));
122122
videoMap.map.project.calls.reset();
123123
videoMap.coordTransfer.toVideoCoordinate.calls.reset();
124-
expect(videoMap.projectVideoMapCoordinate([60, 70])).toBe(projectedPoint);
124+
expect(videoMap.projectVideoMapCoordinate([60, 70])).toEqual(projectedPoint);
125125
expect(videoMap.map.project).toHaveBeenCalledWith(jasmine.objectContaining({ lng: 60, lat: 70 }));
126126
expect(videoMap.coordTransfer.toVideoCoordinate).not.toHaveBeenCalled();
127127
expect(videoMap.transform).toBe(videoMap.map.transform);
@@ -156,6 +156,7 @@ describe('mapboxgl_videoMap', () => {
156156
spyOn(videoMap.coordTransfer, 'toSpatialCoordinate').and.callFake(function (pixel) {
157157
return [11587478 + pixel[0], 3570800 - pixel[1], 0];
158158
});
159+
spyOn(videoMap.coordTransfer, 'toCameraCoordinate').and.returnValue([0, 0, 1]);
159160
var footprint = videoMap.getVideoBounds();
160161
expect(footprint.type).toBe('Feature');
161162
expect(footprint.geometry.type).toBe('Polygon');

0 commit comments

Comments
 (0)