Skip to content
Snippets Groups Projects
Commit f9f34190 authored by Tom Robinson's avatar Tom Robinson
Browse files

Linkify URLs in table view, object detail, etc.

parent f5ab79fa
Branches
Tags
No related merge requests found
......@@ -5,6 +5,7 @@ import Humanize from "humanize";
import React from "react";
import { isDate, isNumber, isCoordinate } from "metabase/lib/schema_metadata";
import { isa, TYPE } from "metabase/lib/types";
import { parseTimestamp } from "metabase/lib/time";
const PRECISION_NUMBER_FORMATTER = d3.format(".2r");
......@@ -98,6 +99,30 @@ function formatTimeWithUnit(value, unit, options = {}) {
}
}
// prevent `javascript:` etc URLs
const URL_WHITELIST_REGEX = /^(https?|mailto):/;
export function formatUrl(value, { jsx } = {}) {
if (jsx && URL_WHITELIST_REGEX.test(value)) {
return (
<a
href={value}
className="link"
// open in a new tab
target="_blank"
// prevent malicious pages from navigating us away
rel="noopener"
// disables quickfilter in tables
onClickCapture={(e) => e.stopPropagation()}
>
{value}
</a>
);
} else {
return value;
}
}
export function formatValue(value, options = {}) {
let column = options.column;
options = {
......@@ -107,6 +132,8 @@ export function formatValue(value, options = {}) {
};
if (value == undefined) {
return null;
} else if (column && isa(column.special_type, TYPE.URL)) {
return formatUrl(value, options);
} else if (column && column.unit != null) {
return formatTimeWithUnit(value, column.unit, options);
} else if (isDate(column) || moment.isDate(value) || moment.isMoment(value) || moment(value, ["YYYY-MM-DD'T'HH:mm:ss.SSSZ"], true).isValid()) {
......
import { formatNumber, formatValue } from 'metabase/lib/formatting';
import { isElementOfType } from "react-addons-test-utils";
import { formatNumber, formatValue, formatUrl } from 'metabase/lib/formatting';
import { TYPE } from "metabase/lib/types";
describe('formatting', () => {
......@@ -54,4 +57,19 @@ describe('formatting', () => {
expect(formatValue(-122.4194, { column: { base_type: TYPE.Number, special_type: TYPE.Longitude }})).toEqual("-122.41940000");
});
});
describe("formatUrl", () => {
it("should return a string when not in jsx mode", () => {
expect(formatUrl("http://metabase.com/")).toEqual("http://metabase.com/")
});
it("should return a component for http:, https:, and mailto: links in jsx mode", () => {
expect(isElementOfType(formatUrl("http://metabase.com/", { jsx: true }), "a")).toEqual(true);
expect(isElementOfType(formatUrl("https://metabase.com/", { jsx: true }), "a")).toEqual(true);
expect(isElementOfType(formatUrl("mailto:tom@metabase.com", { jsx: true }), "a")).toEqual(true);
});
it("should return a string for javascript:, data:, and other links in jsx mode", () => {
expect(formatUrl("javascript:alert('pwnd')", { jsx: true })).toEqual("javascript:alert('pwnd')");
expect(formatUrl("data:text/plain;charset=utf-8,hello%20world", { jsx: true })).toEqual("data:text/plain;charset=utf-8,hello%20world");
});
})
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment