Add text and JSON formatters with real-time progress via traverser hooks, summary statistics, and CLI integration for --show-* and --json flags. Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
multica-agent
parent
876542b2de
commit
5a54834c5f
@@ -0,0 +1,191 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/hits/ExploreDNS/internal/dns"
|
||||
"github.com/hits/ExploreDNS/internal/traverse"
|
||||
)
|
||||
|
||||
type jsonFormatter struct {
|
||||
cfg *Config
|
||||
w io.Writer
|
||||
payload jsonDocument
|
||||
}
|
||||
|
||||
type jsonDocument struct {
|
||||
Domain string `json:"domain"`
|
||||
QueryType string `json:"query_type"`
|
||||
Progress []jsonProgressEvent `json:"progress,omitempty"`
|
||||
Resolves []jsonProgressEvent `json:"resolves,omitempty"`
|
||||
Results []jsonResult `json:"results,omitempty"`
|
||||
Servers []jsonServer `json:"servers,omitempty"`
|
||||
Summary jsonSummary `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
type jsonProgressEvent struct {
|
||||
Stage string `json:"stage"`
|
||||
Depth int `json:"depth"`
|
||||
Name string `json:"name"`
|
||||
QType string `json:"qtype"`
|
||||
Server string `json:"server,omitempty"`
|
||||
Bailiwick string `json:"bailiwick,omitempty"`
|
||||
Resolving bool `json:"resolving,omitempty"`
|
||||
}
|
||||
|
||||
type jsonResult struct {
|
||||
Depth int `json:"depth"`
|
||||
Probability float64 `json:"probability"`
|
||||
ResponseType string `json:"response_type"`
|
||||
Server string `json:"server,omitempty"`
|
||||
Answers []string `json:"answers,omitempty"`
|
||||
CNAMEChain []string `json:"cname_chain,omitempty"`
|
||||
}
|
||||
|
||||
type jsonServer struct {
|
||||
Name string `json:"name"`
|
||||
IPs []string `json:"ips"`
|
||||
}
|
||||
|
||||
type jsonSummary struct {
|
||||
ByType map[string]float64 `json:"by_type,omitempty"`
|
||||
Answers []jsonAnswerStat `json:"answers,omitempty"`
|
||||
}
|
||||
|
||||
type jsonAnswerStat struct {
|
||||
RData string `json:"rdata"`
|
||||
Probability float64 `json:"probability"`
|
||||
Records []string `json:"records,omitempty"`
|
||||
}
|
||||
|
||||
func newJSONFormatter(cfg *Config, w io.Writer) *jsonFormatter {
|
||||
return &jsonFormatter{
|
||||
cfg: cfg,
|
||||
w: w,
|
||||
payload: jsonDocument{
|
||||
Domain: cfg.Domain,
|
||||
QueryType: cfg.QueryType,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) WriteProgress(event traverse.TraversalEvent) error {
|
||||
if !f.cfg.ShowProgress {
|
||||
return nil
|
||||
}
|
||||
f.payload.Progress = append(f.payload.Progress, f.eventToJSON(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) WriteResolve(event traverse.TraversalEvent) error {
|
||||
if !f.cfg.ShowResolves {
|
||||
return nil
|
||||
}
|
||||
f.payload.Resolves = append(f.payload.Resolves, f.eventToJSON(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) WriteResult(result traverse.TraversalResult) error {
|
||||
if !f.cfg.ShowAllStats {
|
||||
return nil
|
||||
}
|
||||
f.payload.Results = append(f.payload.Results, f.resultToJSON(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) WriteSummary(results []traverse.TraversalResult) error {
|
||||
if f.cfg.ShowResults {
|
||||
for _, result := range terminalResults(results) {
|
||||
f.payload.Results = append(f.payload.Results, f.resultToJSON(result))
|
||||
}
|
||||
}
|
||||
|
||||
if f.cfg.ShowServers {
|
||||
servers := collectServers(results)
|
||||
for name, ips := range servers {
|
||||
f.payload.Servers = append(f.payload.Servers, jsonServer{
|
||||
Name: name,
|
||||
IPs: ips,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if f.cfg.ShowSummaryResults {
|
||||
stats := ComputeSummary(results)
|
||||
if stats != nil {
|
||||
f.payload.Summary = jsonSummary{
|
||||
ByType: stats.ByType,
|
||||
}
|
||||
for _, answer := range stats.Answers {
|
||||
f.payload.Summary.Answers = append(f.payload.Summary.Answers, jsonAnswerStat{
|
||||
RData: answer.RData,
|
||||
Probability: answer.Prob,
|
||||
Records: answer.RRs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) Flush() error {
|
||||
enc := json.NewEncoder(f.w)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(f.payload)
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) eventToJSON(event traverse.TraversalEvent) jsonProgressEvent {
|
||||
ref := event.Result.Referral
|
||||
if ref == nil {
|
||||
return jsonProgressEvent{}
|
||||
}
|
||||
|
||||
item := jsonProgressEvent{
|
||||
Stage: stageName(event.Stage),
|
||||
Depth: ref.Depth,
|
||||
Name: trimDomain(ref.Name),
|
||||
QType: dns.QNameType(ref.Qtype),
|
||||
Bailiwick: trimDomain(ref.Bailiwick),
|
||||
Resolving: !ref.HasAddresses(),
|
||||
}
|
||||
if event.Result.Response != nil && event.Result.Response.Server != nil {
|
||||
item.Server = event.Result.Response.Server.String()
|
||||
} else {
|
||||
item.Server = referralServerLabel(ref, event.Result.Response)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) resultToJSON(result traverse.TraversalResult) jsonResult {
|
||||
item := jsonResult{}
|
||||
if result.Referral != nil {
|
||||
item.Depth = result.Referral.Depth
|
||||
item.Probability = result.Referral.Prob
|
||||
}
|
||||
if result.Response != nil {
|
||||
item.ResponseType = result.Response.Type.String()
|
||||
if result.Response.Server != nil {
|
||||
item.Server = result.Response.Server.String()
|
||||
}
|
||||
if result.Response.Decoded != nil {
|
||||
for _, rr := range result.Response.Decoded.Answers {
|
||||
item.Answers = append(item.Answers, dns.FormatRecord(rr))
|
||||
}
|
||||
item.CNAMEChain = append(item.CNAMEChain, result.Response.Decoded.CNAMEChain...)
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func stageName(stage traverse.EventStage) string {
|
||||
switch stage {
|
||||
case traverse.EventStart:
|
||||
return "start"
|
||||
case traverse.EventComplete:
|
||||
return "complete"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user