diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 4c027f570..71960dc02 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -5,4 +5,6 @@ #### Improvements 🧹 +- prevent `tooltip` to be an URL when `link` is already set. [#1091](https://github.com/terrastruct/d2/pull/1091) + #### Bugfixes ⛑️ diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 0654e1567..06dc6578f 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -363,6 +363,13 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Constraint.Value = scalar.ScalarString() attrs.Constraint.MapKey = f.LastPrimaryKey() } + + if attrs.Link != nil && attrs.Tooltip != nil { + _, err := url.ParseRequestURI(attrs.Tooltip.Value) + if err == nil { + c.errorf(scalar, "Tooltip cannot be set to URL when link is also set (for security)") + } + } } func (c *compiler) compileStyle(attrs *d2graph.Attributes, m *d2ir.Map) { diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 584dd5115..845e0c41a 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -1457,6 +1457,40 @@ x -> y: { } }, }, + { + name: "url_tooltip", + text: `x: {tooltip: https://google.com}`, + assertions: func(t *testing.T, g *d2graph.Graph) { + if len(g.Objects) != 1 { + t.Fatal(g.Objects) + } + + if g.Objects[0].Attributes.Tooltip.Value != "https://google.com" { + t.Fatal(g.Objects[0].Attributes.Tooltip.Value) + } + }, + }, + { + name: "no_url_link_and_url_tooltip_concurrently", + text: `x: {link: https://not-google.com; tooltip: https://google.com}`, + expErr: `d2/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.d2:1:44: Tooltip cannot be set to URL when link is also set (for security)`, + }, + { + name: "url_link_and_not_url_tooltip_concurrently", + text: `x: {link: https://google.com; tooltip: hello world}`, + assertions: func(t *testing.T, g *d2graph.Graph) { + if len(g.Objects) != 1 { + t.Fatal(g.Objects) + } + if g.Objects[0].Attributes.Link.Value != "https://google.com" { + t.Fatal(g.Objects[0].Attributes.Link.Value) + } + + if g.Objects[0].Attributes.Tooltip.Value != "hello world" { + t.Fatal(g.Objects[0].Attributes.Tooltip.Value) + } + }, + }, { name: "nil_scope_obj_regression", diff --git a/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.exp.json b/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.exp.json new file mode 100644 index 000000000..289bd8ec8 --- /dev/null +++ b/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.exp.json @@ -0,0 +1,12 @@ +{ + "graph": null, + "err": { + "ioerr": null, + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.d2,0:43:43-0:61:61", + "errmsg": "d2/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.d2:1:44: Tooltip cannot be set to URL when link is also set (for security)" + } + ] + } +} diff --git a/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.exp.json b/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.exp.json new file mode 100644 index 000000000..aeae7d1e2 --- /dev/null +++ b/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.exp.json @@ -0,0 +1,188 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:0:0-0:51:51", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:0:0-0:51:51", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:3:3-0:50:50", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:4:4-0:28:28", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:4:4-0:8:8", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:4:4-0:8:8", + "value": [ + { + "string": "link", + "raw_string": "link" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:10:10-0:28:28", + "value": [ + { + "string": "https://google.com", + "raw_string": "https://google.com" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:30:30-0:50:50", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:30:30-0:37:37", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:30:30-0:37:37", + "value": [ + { + "string": "tooltip", + "raw_string": "tooltip" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:39:39-0:50:50", + "value": [ + { + "string": "hello world", + "raw_string": "hello world" + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "attributes": { + "label": { + "value": "" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "x", + "id_val": "x", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link_and_not_url_tooltip_concurrently.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "style": {}, + "tooltip": { + "value": "hello world" + }, + "link": { + "value": "https://google.com" + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + } + ] + }, + "err": null +} diff --git a/testdata/d2compiler/TestCompile/url_tooltip.exp.json b/testdata/d2compiler/TestCompile/url_tooltip.exp.json new file mode 100644 index 000000000..47234eb10 --- /dev/null +++ b/testdata/d2compiler/TestCompile/url_tooltip.exp.json @@ -0,0 +1,152 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:0:0-0:32:32", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:0:0-0:32:32", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:3:3-0:31:31", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:4:4-0:31:31", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:4:4-0:11:11", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:4:4-0:11:11", + "value": [ + { + "string": "tooltip", + "raw_string": "tooltip" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:13:13-0:31:31", + "value": [ + { + "string": "https://google.com", + "raw_string": "https://google.com" + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "attributes": { + "label": { + "value": "" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "x", + "id_val": "x", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_tooltip.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "style": {}, + "tooltip": { + "value": "https://google.com" + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + } + ] + }, + "err": null +}