mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
TraceView: Add key and url escaping of json tag values (#64331)
This commit is contained in:
parent
b093439b2e
commit
c7a1216cf6
@ -24,7 +24,7 @@ const data = [
|
||||
{ key: 'jsonkey', value: JSON.stringify({ hello: 'world' }) },
|
||||
];
|
||||
|
||||
const setup = (propOverrides?: KeyValuesTableProps) => {
|
||||
const setup = (propOverrides?: Partial<KeyValuesTableProps>) => {
|
||||
const props = {
|
||||
data: data,
|
||||
...propOverrides,
|
||||
@ -89,4 +89,28 @@ describe('KeyValuesTable tests', () => {
|
||||
|
||||
expect(screen.getAllByRole('button')).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('renders a link in json and properly escapes it', () => {
|
||||
setup({
|
||||
data: [
|
||||
{ key: 'jsonkey', value: JSON.stringify({ hello: 'https://example.com"id=x tabindex=1 onfocus=alert(1)' }) },
|
||||
],
|
||||
});
|
||||
const link = screen.getByText(/https:\/\/example.com/);
|
||||
expect(link.tagName).toBe('A');
|
||||
expect(link.attributes.getNamedItem('href')?.value).toBe(
|
||||
'https://example.com%22id=x%20tabindex=1%20onfocus=alert(1)'
|
||||
);
|
||||
});
|
||||
|
||||
it('properly escapes json values', () => {
|
||||
setup({
|
||||
data: [
|
||||
{ key: 'jsonkey', value: JSON.stringify({ '<img src=x onerror=alert(1)>': '<img src=x onerror=alert(1)>' }) },
|
||||
],
|
||||
});
|
||||
const values = screen.getAllByText(/onerror=alert/);
|
||||
expect(values[0].innerHTML).toBe('"<img src=x onerror=alert(1)>":');
|
||||
expect(values[1].innerHTML).toBe('"<img src=x onerror=alert(1)>"');
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import cx from 'classnames';
|
||||
import jsonMarkup from 'json-markup';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@ -25,6 +24,8 @@ import CopyIcon from '../../common/CopyIcon';
|
||||
import { TraceKeyValuePair, TraceLink, TNil } from '../../types';
|
||||
import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles';
|
||||
|
||||
import jsonMarkup from './jsonMarkup';
|
||||
|
||||
const copyIconClassName = 'copyIcon';
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
@ -0,0 +1,133 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2014 Mathias Buus
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
const INDENT = ' ';
|
||||
|
||||
function inlineRule(objRule) {
|
||||
let str = '';
|
||||
objRule &&
|
||||
Object.keys(objRule).forEach(function (rule) {
|
||||
str += rule + ':' + objRule[rule] + ';';
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
function Stylize(styleFile) {
|
||||
function styleClass(cssClass) {
|
||||
return 'class="' + cssClass + '"';
|
||||
}
|
||||
|
||||
function styleInline(cssClass) {
|
||||
return 'style="' + inlineRule(styleFile['.' + cssClass]) + '"';
|
||||
}
|
||||
|
||||
if (!styleFile) {
|
||||
return styleClass;
|
||||
}
|
||||
return styleInline;
|
||||
}
|
||||
|
||||
function type(doc) {
|
||||
if (doc === null) {
|
||||
return 'null';
|
||||
}
|
||||
if (Array.isArray(doc)) {
|
||||
return 'array';
|
||||
}
|
||||
if (typeof doc === 'string' && /^https?:/.test(doc)) {
|
||||
return 'link';
|
||||
}
|
||||
if (typeof doc === 'object' && typeof doc.toISOString === 'function') {
|
||||
return 'date';
|
||||
}
|
||||
|
||||
return typeof doc;
|
||||
}
|
||||
|
||||
function escape(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
module.exports = function (doc, styleFile) {
|
||||
let indent = '';
|
||||
const style = Stylize(styleFile);
|
||||
|
||||
let forEach = function (list, start, end, fn) {
|
||||
if (!list.length) {
|
||||
return start + ' ' + end;
|
||||
}
|
||||
|
||||
let out = start + '\n';
|
||||
|
||||
indent += INDENT;
|
||||
list.forEach(function (key, i) {
|
||||
out += indent + fn(key) + (i < list.length - 1 ? ',' : '') + '\n';
|
||||
});
|
||||
indent = indent.slice(0, -INDENT.length);
|
||||
|
||||
return out + indent + end;
|
||||
};
|
||||
|
||||
function visit(obj) {
|
||||
if (obj === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (type(obj)) {
|
||||
case 'boolean':
|
||||
return '<span ' + style('json-markup-bool') + '>' + obj + '</span>';
|
||||
|
||||
case 'number':
|
||||
return '<span ' + style('json-markup-number') + '>' + obj + '</span>';
|
||||
|
||||
case 'date':
|
||||
return '<span class="json-markup-string">"' + escape(obj.toISOString()) + '"</span>';
|
||||
|
||||
case 'null':
|
||||
return '<span ' + style('json-markup-null') + '>null</span>';
|
||||
|
||||
case 'string':
|
||||
return '<span ' + style('json-markup-string') + '>"' + escape(obj.replace(/\n/g, '\n' + indent)) + '"</span>';
|
||||
|
||||
case 'link':
|
||||
return (
|
||||
'<span ' + style('json-markup-string') + '>"<a href="' + encodeURI(obj) + '">' + escape(obj) + '</a>"</span>'
|
||||
);
|
||||
|
||||
case 'array':
|
||||
return forEach(obj, '[', ']', visit);
|
||||
|
||||
case 'object':
|
||||
const keys = Object.keys(obj).filter(function (key) {
|
||||
return obj[key] !== undefined;
|
||||
});
|
||||
|
||||
return forEach(keys, '{', '}', function (key) {
|
||||
return '<span ' + style('json-markup-key') + '>"' + escape(key) + '":</span> ' + visit(obj[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div ' + style('json-markup') + '>' + visit(doc) + '</div>';
|
||||
};
|
Loading…
Reference in New Issue
Block a user