Skip to content

Commit c7ac906

Browse files
committed
fix: resolve Go Report Card gofmt and gocyclo issues
- Apply gofmt -s to internal/tui/form.go and scan_view.go - Refactor run() in main.go (complexity 42 -> ~15) by extracting: parseFlags() into cliFlags struct validateFlags() for input validation openOutputFile() for output file setup logVerboseStart() and logVerboseDone() for verbose output - All tests pass Made-with: Cursor
1 parent 6f86ee5 commit c7ac906

File tree

2 files changed

+154
-117
lines changed

2 files changed

+154
-117
lines changed

internal/tui/form.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ func newFormModel(saved savedConfig) formModel {
9090

9191
// inputs[0..6] correspond to the non-toggle fields.
9292
m.inputs = []textinput.Model{
93-
newInput("e.g. example.com", str(saved.Domain, "")), // 0 Domain
93+
newInput("e.g. example.com", str(saved.Domain, "")), // 0 Domain
9494
newInput("e.g. examples/sample_wordlist.txt", str(saved.Wordlist, "examples/sample_wordlist.txt")), // 1 Wordlist
95-
newInput("1–100", intStr(saved.HitRate, "15")), // 2 HitRate
96-
newInput("e.g. 8.8.8.8:53", str(saved.DNSServer, "8.8.8.8:53")), // 3 DNSServer
97-
newInput("e.g. 100", intStr(saved.Concurrency, "100")), // 4 Concurrency
98-
newInput("e.g. 1000", intStr(saved.TimeoutMs, "1000")), // 5 Timeout
99-
newInput("e.g. 1", intStr(saved.Attempts, "1")), // 6 Attempts
95+
newInput("1–100", intStr(saved.HitRate, "15")), // 2 HitRate
96+
newInput("e.g. 8.8.8.8:53", str(saved.DNSServer, "8.8.8.8:53")), // 3 DNSServer
97+
newInput("e.g. 100", intStr(saved.Concurrency, "100")), // 4 Concurrency
98+
newInput("e.g. 1000", intStr(saved.TimeoutMs, "1000")), // 5 Timeout
99+
newInput("e.g. 1", intStr(saved.Attempts, "1")), // 6 Attempts
100100
}
101101

102102
m.toggles[0] = saved.Simulate

main.go

Lines changed: 148 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -85,33 +85,142 @@ func main() {
8585
os.Exit(run())
8686
}
8787

88-
func run() int {
88+
// cliFlags holds all parsed command-line flag values.
89+
type cliFlags struct {
90+
wordlistFile string
91+
concurrency int
92+
timeoutMs int
93+
dnsServer string
94+
verbose bool
95+
showVersion bool
96+
showProgress bool
97+
testMode bool
98+
testHitRate int
99+
outputFile string
100+
attempts int
101+
retries int
102+
force bool
103+
}
104+
105+
func parseFlags() cliFlags {
106+
var f cliFlags
89107
flag.Bool("tui", false, "Launch the interactive terminal UI (all other flags are ignored)")
90-
wordlistFile := flag.String("w", "", "Path to the wordlist file")
91-
concurrency := flag.Int("t", 100, "Number of concurrent workers")
92-
timeoutMs := flag.Int("timeout", 1000, "DNS lookup timeout in milliseconds")
93-
dnsServer := flag.String("dns-server", DefaultDNSServer, "DNS server to use (format: ip:port)")
94-
verbose := flag.Bool("v", false, "Enable verbose output")
95-
showVersion := flag.Bool("version", false, "Show version information")
96-
showProgress := flag.Bool("progress", true, "Show progress during scanning")
97-
testMode := flag.Bool("simulate", false, "Run in simulation mode without actual DNS queries (for testing)")
98-
testHitRate := flag.Int("hit-rate", 15, "In simulation mode, percentage of subdomains that will 'resolve' (1-100)")
99-
outputFile := flag.String("o", "", "Write results to file (in addition to stdout)")
100-
attempts := flag.Int("attempts", 0, "Total DNS resolution attempts per subdomain (1 = no retry)")
101-
retries := flag.Int("retries", 0, "Deprecated: use -attempts instead")
102-
force := flag.Bool("force", false, "Continue scanning even if wildcard DNS is detected")
108+
flag.StringVar(&f.wordlistFile, "w", "", "Path to the wordlist file")
109+
flag.IntVar(&f.concurrency, "t", 100, "Number of concurrent workers")
110+
flag.IntVar(&f.timeoutMs, "timeout", 1000, "DNS lookup timeout in milliseconds")
111+
flag.StringVar(&f.dnsServer, "dns-server", DefaultDNSServer, "DNS server to use (format: ip:port)")
112+
flag.BoolVar(&f.verbose, "v", false, "Enable verbose output")
113+
flag.BoolVar(&f.showVersion, "version", false, "Show version information")
114+
flag.BoolVar(&f.showProgress, "progress", true, "Show progress during scanning")
115+
flag.BoolVar(&f.testMode, "simulate", false, "Run in simulation mode without actual DNS queries (for testing)")
116+
flag.IntVar(&f.testHitRate, "hit-rate", 15, "In simulation mode, percentage of subdomains that will 'resolve' (1-100)")
117+
flag.StringVar(&f.outputFile, "o", "", "Write results to file (in addition to stdout)")
118+
flag.IntVar(&f.attempts, "attempts", 0, "Total DNS resolution attempts per subdomain (1 = no retry)")
119+
flag.IntVar(&f.retries, "retries", 0, "Deprecated: use -attempts instead")
120+
flag.BoolVar(&f.force, "force", false, "Continue scanning even if wildcard DNS is detected")
103121
flag.Parse()
122+
return f
123+
}
104124

105-
maxAttempts, err := resolveAttempts(*attempts, *retries)
125+
func validateFlags(f cliFlags, out *output.Writer, maxAttempts int) (string, bool) {
126+
if f.wordlistFile == "" || flag.NArg() == 0 {
127+
fmt.Fprintln(os.Stderr, "Usage: subenum -w <wordlist_file> [options] <domain>")
128+
flag.PrintDefaults()
129+
return "", false
130+
}
131+
if f.concurrency <= 0 {
132+
out.Error("Concurrency level (-t) must be greater than 0")
133+
return "", false
134+
}
135+
if f.timeoutMs <= 0 {
136+
out.Error("Timeout (-timeout) must be greater than 0")
137+
return "", false
138+
}
139+
if f.testHitRate < 1 || f.testHitRate > 100 {
140+
out.Error("Hit rate (-hit-rate) must be between 1 and 100")
141+
return "", false
142+
}
143+
if maxAttempts < 1 {
144+
out.Error("Attempts (-attempts) must be at least 1")
145+
return "", false
146+
}
147+
if !f.testMode {
148+
if err := validateDNSServer(f.dnsServer); err != nil {
149+
out.Error("DNS server %s: %v", f.dnsServer, err)
150+
return "", false
151+
}
152+
}
153+
domain := flag.Arg(0)
154+
if err := validateDomain(domain); err != nil {
155+
out.Error("%v", err)
156+
return "", false
157+
}
158+
return domain, true
159+
}
160+
161+
func openOutputFile(path string, testMode bool, out *output.Writer) (*output.Writer, *bufio.Writer, *os.File, bool) {
162+
if path == "" {
163+
return out, nil, nil, true
164+
}
165+
f, err := os.Create(path)
166+
if err != nil {
167+
out.Error("creating output file: %v", err)
168+
return out, nil, nil, false
169+
}
170+
w := bufio.NewWriter(f)
171+
return output.New(w, testMode), w, f, true
172+
}
106173

107-
out := output.New(nil, *testMode)
174+
func logVerboseStart(f cliFlags, domain string, maxAttempts int, out *output.Writer) {
175+
out.Info("Starting %s v%s", ProgramName, Version)
176+
if f.testMode {
177+
out.Info("Mode: SIMULATION (no actual DNS queries)")
178+
out.Info("Simulated hit rate: %d%%", f.testHitRate)
179+
} else {
180+
out.Info("Mode: LIVE DNS RESOLUTION")
181+
}
182+
out.Info("Target domain: %s", domain)
183+
out.Info("Wordlist: %s", f.wordlistFile)
184+
out.Info("Concurrency: %d workers", f.concurrency)
185+
out.Info("Timeout: %d ms", f.timeoutMs)
186+
out.Info("Attempts: %d", maxAttempts)
187+
if !f.testMode {
188+
out.Info("DNS Server: %s", f.dnsServer)
189+
}
190+
if f.outputFile != "" {
191+
out.Info("Output file: %s", f.outputFile)
192+
}
193+
out.Info("---")
194+
}
195+
196+
func logVerboseDone(ev scan.Event, f cliFlags, outWriter *bufio.Writer, out *output.Writer) {
197+
out.Info("\nScan completed for %s", flag.Arg(0))
198+
out.Info("Processed %d subdomain prefixes", ev.Processed)
199+
if f.testMode {
200+
out.Info("Found %d simulated subdomains", ev.Found)
201+
} else {
202+
out.Info("Found %d subdomains", ev.Found)
203+
}
204+
if outWriter != nil {
205+
out.Info("Results written to: %s", f.outputFile)
206+
}
207+
if f.testMode {
208+
out.Info("\nNOTE: Results were simulated and no actual DNS queries were performed.")
209+
out.Info("This mode is intended for educational and testing purposes only.")
210+
}
211+
}
108212

213+
func run() int {
214+
f := parseFlags()
215+
216+
maxAttempts, err := resolveAttempts(f.attempts, f.retries)
217+
out := output.New(nil, f.testMode)
109218
if err != nil {
110219
out.Error("%v", err)
111220
return 1
112221
}
113222

114-
if *testMode {
223+
if f.testMode {
115224
out.Info("")
116225
out.Info("╔════════════════════════════════════════════════════════════════════╗")
117226
out.Info("║ SIMULATION MODE ACTIVE - NO ACTUAL DNS QUERIES WILL BE PERFORMED ║")
@@ -120,105 +229,46 @@ func run() int {
120229
out.Info("")
121230
}
122231

123-
if *showVersion {
232+
if f.showVersion {
124233
fmt.Fprintf(os.Stderr, "%s v%s\n", ProgramName, Version)
125-
if *testMode {
234+
if f.testMode {
126235
fmt.Fprintln(os.Stderr, "Running in SIMULATION mode")
127236
}
128237
return 0
129238
}
130239

131-
if *wordlistFile == "" || flag.NArg() == 0 {
132-
fmt.Fprintln(os.Stderr, "Usage: subenum -w <wordlist_file> [options] <domain>")
133-
flag.PrintDefaults()
134-
return 1
135-
}
136-
137-
if *concurrency <= 0 {
138-
out.Error("Concurrency level (-t) must be greater than 0")
139-
return 1
140-
}
141-
142-
if *timeoutMs <= 0 {
143-
out.Error("Timeout (-timeout) must be greater than 0")
144-
return 1
145-
}
146-
147-
if *testHitRate < 1 || *testHitRate > 100 {
148-
out.Error("Hit rate (-hit-rate) must be between 1 and 100")
149-
return 1
150-
}
151-
152-
if maxAttempts < 1 {
153-
out.Error("Attempts (-attempts) must be at least 1")
240+
domain, ok := validateFlags(f, out, maxAttempts)
241+
if !ok {
154242
return 1
155243
}
156244

157-
if !*testMode {
158-
if err := validateDNSServer(*dnsServer); err != nil {
159-
out.Error("DNS server %s: %v", *dnsServer, err)
160-
return 1
161-
}
162-
}
163-
164-
domain := flag.Arg(0)
165-
if err := validateDomain(domain); err != nil {
166-
out.Error("%v", err)
245+
out, outWriter, outFile, ok := openOutputFile(f.outputFile, f.testMode, out)
246+
if !ok {
167247
return 1
168248
}
169-
170-
timeout := time.Duration(*timeoutMs) * time.Millisecond
171-
172-
var outWriter *bufio.Writer
173-
if *outputFile != "" {
174-
f, err := os.Create(*outputFile)
175-
if err != nil {
176-
out.Error("creating output file: %v", err)
177-
return 1
178-
}
179-
outWriter = bufio.NewWriter(f)
180-
out = output.New(outWriter, *testMode)
249+
if outFile != nil {
181250
defer func() {
182251
if flushErr := outWriter.Flush(); flushErr != nil {
183252
out.Error("flushing output: %v", flushErr)
184253
}
185-
if closeErr := f.Close(); closeErr != nil {
254+
if closeErr := outFile.Close(); closeErr != nil {
186255
out.Error("closing output file: %v", closeErr)
187256
}
188257
}()
189258
}
190259

191-
if *verbose {
192-
out.Info("Starting %s v%s", ProgramName, Version)
193-
if *testMode {
194-
out.Info("Mode: SIMULATION (no actual DNS queries)")
195-
out.Info("Simulated hit rate: %d%%", *testHitRate)
196-
} else {
197-
out.Info("Mode: LIVE DNS RESOLUTION")
198-
}
199-
out.Info("Target domain: %s", domain)
200-
out.Info("Wordlist: %s", *wordlistFile)
201-
out.Info("Concurrency: %d workers", *concurrency)
202-
out.Info("Timeout: %d ms", *timeoutMs)
203-
out.Info("Attempts: %d", maxAttempts)
204-
if !*testMode {
205-
out.Info("DNS Server: %s", *dnsServer)
206-
}
207-
if *outputFile != "" {
208-
out.Info("Output file: %s", *outputFile)
209-
}
210-
out.Info("---")
260+
if f.verbose {
261+
logVerboseStart(f, domain, maxAttempts, out)
211262
}
212263

213-
entries, duplicates, err := wordlist.LoadWordlist(*wordlistFile)
264+
entries, duplicates, err := wordlist.LoadWordlist(f.wordlistFile)
214265
if err != nil {
215266
out.Error("reading wordlist file: %v", err)
216267
return 1
217268
}
218269

219270
totalWords := int64(len(entries))
220-
221-
if *verbose {
271+
if f.verbose {
222272
out.Info("Total wordlist entries: %d", totalWords)
223273
if duplicates > 0 {
224274
out.Info("Removed %d duplicate wordlist entries", duplicates)
@@ -243,14 +293,14 @@ func run() int {
243293
cfg := scan.Config{
244294
Domain: domain,
245295
Entries: entries,
246-
Concurrency: *concurrency,
247-
Timeout: timeout,
248-
DNSServer: *dnsServer,
249-
Simulate: *testMode,
250-
HitRate: *testHitRate,
296+
Concurrency: f.concurrency,
297+
Timeout: time.Duration(f.timeoutMs) * time.Millisecond,
298+
DNSServer: f.dnsServer,
299+
Simulate: f.testMode,
300+
HitRate: f.testHitRate,
251301
Attempts: maxAttempts,
252-
Force: *force,
253-
Verbose: *verbose,
302+
Force: f.force,
303+
Verbose: f.verbose,
254304
}
255305

256306
events := make(chan scan.Event, 64)
@@ -262,7 +312,7 @@ func run() int {
262312
case scan.EventResult:
263313
out.Result(ev.Domain)
264314
case scan.EventProgress:
265-
if *showProgress && totalWords > 0 {
315+
if f.showProgress && totalWords > 0 {
266316
progressStarted = true
267317
pct := float64(ev.Processed) / float64(ev.Total) * 100
268318
out.Progress(pct, ev.Processed, ev.Total, ev.Found)
@@ -279,21 +329,8 @@ func run() int {
279329
if progressStarted {
280330
out.ProgressDone()
281331
}
282-
if *verbose {
283-
out.Info("\nScan completed for %s", domain)
284-
out.Info("Processed %d subdomain prefixes", ev.Processed)
285-
if *testMode {
286-
out.Info("Found %d simulated subdomains", ev.Found)
287-
} else {
288-
out.Info("Found %d subdomains", ev.Found)
289-
}
290-
if outWriter != nil {
291-
out.Info("Results written to: %s", *outputFile)
292-
}
293-
if *testMode {
294-
out.Info("\nNOTE: Results were simulated and no actual DNS queries were performed.")
295-
out.Info("This mode is intended for educational and testing purposes only.")
296-
}
332+
if f.verbose {
333+
logVerboseDone(ev, f, outWriter, out)
297334
}
298335
}
299336
}

0 commit comments

Comments
 (0)