diff --git a/.betterer.results b/.betterer.results index 10f429026a8..78d2c5cef17 100644 --- a/.betterer.results +++ b/.betterer.results @@ -116,7 +116,7 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js:1966455998": [ [14, 17, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:3813002651": [ + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:3568627238": [ [14, 19, 13, "RegExp match", "2409514259"] ], "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js:3006381933": [ diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js index 4f2ec783abd..e17d6a92836 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js @@ -102,4 +102,22 @@ describe('', () => { } }); }); + + it('properly escapes values', () => { + const data = [ + { + key: 'jsonkey', + value: JSON.stringify({ + '': '', + url: 'https://example.com"id=x tabindex=1 onfocus=alert(1)', + }), + }, + ]; + const wrapper = shallow(); + const el = wrapper.find(`.${ubInlineBlock}`); + expect(el.length).toBe(1); + expect(el.html().replace(/\n/g, '')).toMatch( + `
{ \"<img src=x onerror=alert(1)>\": \"<img src=x onerror=alert(1)>\", \"url\": \"https://example.com"id=x tabindex=1 onfocus=alert(1)\"}
` + ); + }); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index 9e17ff31070..826233572e1 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -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'; @@ -26,6 +25,8 @@ import { TNil } from '../../types'; import { TraceKeyValuePair, TraceLink } from '../../types/trace'; import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles'; +import jsonMarkup from './jsonMarkup'; + const copyIconClassName = 'copyIcon'; export const getStyles = (theme: GrafanaTheme2) => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/jsonMarkup.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/jsonMarkup.js new file mode 100644 index 00000000000..2dcbb1ee895 --- /dev/null +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/jsonMarkup.js @@ -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, '"'); +} + +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 '' + obj + ''; + + case 'number': + return '' + obj + ''; + + case 'date': + return '"' + escape(obj.toISOString()) + '"'; + + case 'null': + return 'null'; + + case 'string': + return '"' + escape(obj.replace(/\n/g, '\n' + indent)) + '"'; + + case 'link': + return ( + '"' + escape(obj) + '"' + ); + + 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 '"' + escape(key) + '": ' + visit(obj[key]); + }); + } + + return ''; + } + + return '
' + visit(doc) + '
'; +};