Skip to content

Commit 4eb167f

Browse files
committed
feat: d2-lang support
1 parent 59491b3 commit 4eb167f

File tree

7 files changed

+357
-3
lines changed

7 files changed

+357
-3
lines changed

cmd/root.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ var workflow formatter.Workflow
5252

5353
// rootCmd represents the base command when called without any subcommands
5454
var rootCmd = &cobra.Command{
55-
Use: "nmap-formatter [html|csv|md|json|dot|sqlite|excel] [path-to-nmap.xml]",
55+
Use: "nmap-formatter [html|csv|md|json|dot|sqlite|excel|d2] [path-to-nmap.xml]",
5656
Short: "Utility that can help you to convert NMAP XML application output to various other formats",
57-
Long: `This utility allows you to convert NMAP XML output to various other formats like (html, csv, markdown (md), json, dot, excel, sqlite)`,
57+
Long: `This utility allows you to convert NMAP XML output to various other formats like (html, csv, markdown (md), json, dot, excel, sqlite, d2)`,
5858
Args: arguments,
5959
RunE: run,
6060
}
@@ -114,6 +114,9 @@ func init() {
114114
rootCmd.Flags().StringVar(&config.OutputOptions.SqliteOutputOptions.DSN, "sqlite-dsn", "nmap.sqlite", "--sqlite-dsn nmap.sqlite")
115115
rootCmd.Flags().StringVar(&config.OutputOptions.SqliteOutputOptions.ScanIdentifier, "scan-id", "", "--scan-id abc123")
116116

117+
// Configs related to D2 language
118+
rootCmd.Flags().BoolVar(&config.OutputOptions.D2LangOptions.SkipDownHosts, "d2-skip-down-hosts", true, "--d2-skip-down-hosts=false, would print all hosts that are offline in D2 language output")
119+
117120
workflow = &formatter.MainWorkflow{}
118121
}
119122

formatter/format.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ const (
1818
SqliteOutput OutputFormat = "sqlite"
1919
// ExcelOutput constant defines OutputFormat for Excel file, which can be used to generate Excel files
2020
ExcelOutput OutputFormat = "excel"
21+
// D2LangOutput constant defines OutputFormat for D2 language, which can be used to generate D2 language files
22+
D2LangOutput OutputFormat = "d2"
2123
)
2224

2325
// IsValid checks whether requested output format is valid
2426
func (of OutputFormat) IsValid() bool {
2527
// markdown & md is essentially the same thing
2628
switch of {
27-
case "markdown", "md", "html", "csv", "json", "dot", "sqlite", "excel":
29+
case "markdown", "md", "html", "csv", "json", "dot", "sqlite", "excel", "d2":
2830
return true
2931
}
3032
return false

formatter/formatter.go

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func New(config *Config) Formatter {
3737
return &SqliteFormatter{
3838
config,
3939
}
40+
case D2LangOutput:
41+
return &D2LangFormatter{
42+
config,
43+
}
4044
}
4145
return nil
4246
}

formatter/formatter_d2.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package formatter
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"fmt"
7+
"hash/fnv"
8+
9+
"oss.terrastruct.com/d2/d2format"
10+
"oss.terrastruct.com/d2/d2graph"
11+
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
12+
"oss.terrastruct.com/d2/d2lib"
13+
"oss.terrastruct.com/d2/d2oracle"
14+
"oss.terrastruct.com/d2/lib/log"
15+
"oss.terrastruct.com/d2/lib/textmeasure"
16+
)
17+
18+
type D2LangFormatter struct {
19+
config *Config
20+
}
21+
22+
func (f *D2LangFormatter) Format(td *TemplateData, templateContent string) (err error) {
23+
ruler, _ := textmeasure.NewRuler()
24+
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
25+
return d2dagrelayout.DefaultLayout, nil
26+
}
27+
compileOpts := &d2lib.CompileOptions{
28+
LayoutResolver: layoutResolver,
29+
Ruler: ruler,
30+
}
31+
32+
_, graph, _ := d2lib.Compile(log.Stderr(context.Background()), "nmap", compileOpts, nil)
33+
34+
for i := range td.NMAPRun.Host {
35+
host := &td.NMAPRun.Host[i]
36+
fnv := fnv.New128()
37+
38+
if host.ShouldSkipHost(td.OutputOptions.D2LangOptions.SkipDownHosts) {
39+
continue
40+
}
41+
42+
address := host.JoinedAddresses("/")
43+
hostnames := host.JoinedHostNames("/")
44+
hostLabel := address
45+
if hostnames != "" {
46+
hostLabel = fmt.Sprintf("%s\n(%s)", address, hostnames)
47+
}
48+
_, err := fnv.Write([]byte(address))
49+
if err != nil {
50+
return err
51+
}
52+
53+
hostId := hex.EncodeToString(fnv.Sum(nil))
54+
graph, _, _ = d2oracle.Create(graph, nil, hostId)
55+
graph, _ = d2oracle.Set(graph, nil, hostId+".label", nil, &hostLabel)
56+
graph, _ = d2oracle.Set(graph, nil, "nmap -> "+hostId, nil, nil)
57+
58+
for j := range host.Port {
59+
port := &host.Port[j]
60+
portId := fmt.Sprintf("%s-port%d", hostId, port.PortID)
61+
graph, _, _ = d2oracle.Create(graph, nil, portId)
62+
portLabel := fmt.Sprintf("%d/%s\n%s\n%s", port.PortID, port.Protocol, port.State.State, port.Service.Name)
63+
graph, _ = d2oracle.Set(graph, nil, portId+".label", nil, &portLabel)
64+
shape := "circle"
65+
graph, _ = d2oracle.Set(graph, nil, portId+".shape", nil, &shape)
66+
width := "25"
67+
graph, _ = d2oracle.Set(graph, nil, portId+".width", nil, &width)
68+
graph, _ = d2oracle.Move(graph, nil, portId, hostId+"."+portId, true)
69+
}
70+
}
71+
_, err = f.config.Writer.Write([]byte(d2format.Format(graph.AST)))
72+
return
73+
}
74+
75+
// defaultTemplateContent does not return anything in this case
76+
func (f *D2LangFormatter) defaultTemplateContent() string {
77+
return ""
78+
}

formatter/output_options.go

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type OutputOptions struct {
88
CSVOptions CSVOutputOptions
99
SqliteOutputOptions SqliteOutputOptions
1010
ExcelOptions ExcelOutputOptions
11+
D2LangOptions D2LangOutputOptions
1112
}
1213

1314
// HTMLOutputOptions stores options related only to HTML conversion/formatting
@@ -68,3 +69,9 @@ type ExcelOutputOptions struct {
6869
// The hosts that are down won't be displayed
6970
SkipDownHosts bool
7071
}
72+
73+
// D2LangOutputOptions store options related to D2 language file formatting
74+
type D2LangOutputOptions struct {
75+
// The hosts that are down won't be displayed
76+
SkipDownHosts bool
77+
}

go.mod

+27
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,43 @@ require (
77
github.com/mattn/go-sqlite3 v1.14.22
88
github.com/spf13/cobra v1.8.0
99
golang.org/x/net v0.24.0
10+
oss.terrastruct.com/d2 v0.6.3
1011
)
1112

1213
require (
14+
cdr.dev/slog v1.4.2-0.20221206192828-e4803b10ae17 // indirect
15+
github.com/PuerkitoBio/goquery v1.8.1 // indirect
16+
github.com/alecthomas/chroma v0.10.0 // indirect
17+
github.com/alecthomas/chroma/v2 v2.5.0 // indirect
18+
github.com/andybalholm/cascadia v1.3.2 // indirect
19+
github.com/dlclark/regexp2 v1.10.0 // indirect
20+
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d // indirect
21+
github.com/fatih/color v1.13.0 // indirect
22+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
23+
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
24+
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
25+
github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect
26+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
27+
github.com/mattn/go-colorable v0.1.9 // indirect
28+
github.com/mattn/go-isatty v0.0.20 // indirect
29+
github.com/mazznoer/csscolorparser v0.1.3 // indirect
1330
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
1431
github.com/richardlehane/mscfb v1.0.4 // indirect
1532
github.com/richardlehane/msoleps v1.0.3 // indirect
33+
github.com/rivo/uniseg v0.4.4 // indirect
1634
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
1735
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
36+
github.com/yuin/goldmark v1.6.0 // indirect
37+
go.opencensus.io v0.24.0 // indirect
1838
golang.org/x/crypto v0.22.0 // indirect
39+
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
40+
golang.org/x/image v0.14.0 // indirect
41+
golang.org/x/sys v0.19.0 // indirect
42+
golang.org/x/term v0.19.0 // indirect
1943
golang.org/x/text v0.14.0 // indirect
44+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
45+
gonum.org/v1/plot v0.14.0 // indirect
46+
oss.terrastruct.com/util-go v0.0.0-20231101220827-55b3812542c2 // indirect
2047
)
2148

2249
require (

0 commit comments

Comments
 (0)