Skip to content

Commit f61d64c

Browse files
committed
master
1 parent 765df12 commit f61d64c

1 file changed

Lines changed: 253 additions & 9 deletions

File tree

cmd/securetrace/main.go

Lines changed: 253 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"os/signal"
88
"strings"
9+
"sync"
910
"syscall"
1011
"time"
1112

@@ -16,6 +17,21 @@ import (
1617
"github.com/ismailtasdelen/securetrace/pkg/types"
1718
)
1819

20+
// ANSI color codes
21+
const (
22+
colorReset = "\033[0m"
23+
colorBold = "\033[1m"
24+
colorDim = "\033[2m"
25+
colorRed = "\033[31m"
26+
colorGreen = "\033[32m"
27+
colorYellow = "\033[33m"
28+
colorBlue = "\033[34m"
29+
colorMagenta = "\033[35m"
30+
colorCyan = "\033[36m"
31+
colorWhite = "\033[37m"
32+
colorBgBlue = "\033[44m"
33+
)
34+
1935
// CLI flags
2036
var (
2137
cfgFile string
@@ -30,26 +46,32 @@ var (
3046
noColor bool
3147
concurrency int
3248
retries int
49+
useColors bool
3350
)
3451

3552
func main() {
3653
if err := run(); err != nil {
37-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
54+
if useColors {
55+
fmt.Fprintf(os.Stderr, "%s✗ Error:%s %v\n", colorRed, colorReset, err)
56+
} else {
57+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
58+
}
3859
os.Exit(1)
3960
}
4061
}
4162

4263
func run() error {
4364
// Parse arguments
4465
args := os.Args[1:]
66+
useColors = isTerminal()
4567

4668
// Handle help and version
4769
if len(args) == 0 || args[0] == "-h" || args[0] == "--help" {
4870
printUsage()
4971
return nil
5072
}
5173

52-
if args[0] == "version" || args[0] == "-v" || args[0] == "--version" {
74+
if args[0] == "version" || args[0] == "-V" || args[0] == "--version" {
5375
printVersion()
5476
return nil
5577
}
@@ -120,6 +142,7 @@ func run() error {
120142
continue
121143
case arg == "--no-color":
122144
noColor = true
145+
useColors = false
123146
i++
124147
continue
125148
case arg == "--config":
@@ -153,13 +176,15 @@ func run() error {
153176
retries = 3
154177
}
155178
followRedir = true // Default to following redirects unless --no-redirect
179+
verifyTLS = true // Default to verify TLS
156180

157181
// Setup logger
158182
if verbose {
159183
logger.SetLevel(logger.DEBUG)
160184
}
161185
if noColor {
162186
logger.SetColored(false)
187+
useColors = false
163188
}
164189

165190
// Load config
@@ -191,6 +216,11 @@ func run() error {
191216
return fmt.Errorf("no URLs provided. Usage: securetrace [options] <url> [url...]")
192217
}
193218

219+
// Print banner for text output
220+
if outputFmt == "text" && isTerminal() {
221+
printBanner()
222+
}
223+
194224
// Setup context with cancellation
195225
ctx, cancel := context.WithCancel(context.Background())
196226
defer cancel()
@@ -200,7 +230,11 @@ func run() error {
200230
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
201231
go func() {
202232
<-sigChan
203-
fmt.Println("\nInterrupted, cleaning up...")
233+
if useColors {
234+
fmt.Printf("\n%s⚠ Interrupted, cleaning up...%s\n", colorYellow, colorReset)
235+
} else {
236+
fmt.Println("\nInterrupted, cleaning up...")
237+
}
204238
cancel()
205239
}()
206240

@@ -209,23 +243,42 @@ func run() error {
209243
defer t.Close()
210244

211245
// Get reporter
212-
colored := !noColor && isTerminal()
246+
colored := useColors && !noColor
213247
rep := reporter.GetReporter(reporter.ParseFormat(outputFmt), colored)
214248

249+
// Show progress for text output
250+
var spinner *Spinner
251+
if outputFmt == "text" && isTerminal() {
252+
spinner = NewSpinner("Analyzing")
253+
spinner.Start()
254+
}
255+
215256
// Perform trace(s)
216257
var output []byte
217258
var err error
218259

219260
if len(urls) == 1 {
220261
// Single URL trace
262+
if spinner != nil {
263+
spinner.SetMessage(fmt.Sprintf("Tracing %s", truncateURL(urls[0], 50)))
264+
}
221265
result, traceErr := t.Trace(ctx, urls[0])
266+
if spinner != nil {
267+
spinner.Stop()
268+
}
222269
if traceErr != nil && result == nil {
223270
return traceErr
224271
}
225272
output, err = rep.Format(result)
226273
} else {
227274
// Multiple URLs - concurrent scan
275+
if spinner != nil {
276+
spinner.SetMessage(fmt.Sprintf("Scanning %d targets", len(urls)))
277+
}
228278
result := t.TraceMultiple(ctx, urls, concurrency)
279+
if spinner != nil {
280+
spinner.Stop()
281+
}
229282
output, err = rep.FormatScan(result)
230283
}
231284

@@ -238,22 +291,147 @@ func run() error {
238291
if err := os.WriteFile(outputFile, output, 0644); err != nil {
239292
return fmt.Errorf("failed to write output file: %w", err)
240293
}
241-
fmt.Printf("Report saved to: %s\n", outputFile)
294+
if useColors {
295+
fmt.Printf("%s✓ Report saved to:%s %s\n", colorGreen, colorReset, outputFile)
296+
} else {
297+
fmt.Printf("Report saved to: %s\n", outputFile)
298+
}
242299
} else {
243300
fmt.Print(string(output))
244301
}
245302

246303
return nil
247304
}
248305

306+
func printBanner() {
307+
if useColors {
308+
fmt.Printf(`
309+
%s╔═══════════════════════════════════════════════════════════════╗%s
310+
%s║%s %s███████╗███████╗ ██████╗██╗ ██╗██████╗ ███████╗%s %s║%s
311+
%s║%s %s██╔════╝██╔════╝██╔════╝██║ ██║██╔══██╗██╔════╝%s %s║%s
312+
%s║%s %s███████╗█████╗ ██║ ██║ ██║██████╔╝█████╗%s %s║%s
313+
%s║%s %s╚════██║██╔══╝ ██║ ██║ ██║██╔══██╗██╔══╝%s %s║%s
314+
%s║%s %s███████║███████╗╚██████╗╚██████╔╝██║ ██║███████╗%s %s║%s
315+
%s║%s %s╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝%s %s║%s
316+
%s║%s %s║%s
317+
%s║%s %s████████╗██████╗ █████╗ ██████╗███████╗%s %s║%s
318+
%s║%s %s╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝%s %s║%s
319+
%s║%s %s ██║ ██████╔╝███████║██║ █████╗%s %s║%s
320+
%s║%s %s ██║ ██╔══██╗██╔══██║██║ ██╔══╝%s %s║%s
321+
%s║%s %s ██║ ██║ ██║██║ ██║╚██████╗███████╗%s %s║%s
322+
%s║%s %s ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚══════╝%s %s║%s
323+
%s║%s %s║%s
324+
%s║%s %s🔒 HTTP/HTTPS Security Analysis Tool%s %s║%s
325+
%s║%s %s📌 Version: %s%s %s║%s
326+
%s╚═══════════════════════════════════════════════════════════════╝%s
327+
328+
`,
329+
colorCyan, colorReset,
330+
colorCyan, colorReset, colorBold+colorBlue, colorReset, colorCyan, colorReset,
331+
colorCyan, colorReset, colorBold+colorBlue, colorReset, colorCyan, colorReset,
332+
colorCyan, colorReset, colorBold+colorBlue, colorReset, colorCyan, colorReset,
333+
colorCyan, colorReset, colorBold+colorBlue, colorReset, colorCyan, colorReset,
334+
colorCyan, colorReset, colorBold+colorBlue, colorReset, colorCyan, colorReset,
335+
colorCyan, colorReset, colorBold+colorBlue, colorReset, colorCyan, colorReset,
336+
colorCyan, colorReset, colorCyan, colorReset,
337+
colorCyan, colorReset, colorBold+colorMagenta, colorReset, colorCyan, colorReset,
338+
colorCyan, colorReset, colorBold+colorMagenta, colorReset, colorCyan, colorReset,
339+
colorCyan, colorReset, colorBold+colorMagenta, colorReset, colorCyan, colorReset,
340+
colorCyan, colorReset, colorBold+colorMagenta, colorReset, colorCyan, colorReset,
341+
colorCyan, colorReset, colorBold+colorMagenta, colorReset, colorCyan, colorReset,
342+
colorCyan, colorReset, colorBold+colorMagenta, colorReset, colorCyan, colorReset,
343+
colorCyan, colorReset, colorCyan, colorReset,
344+
colorCyan, colorReset, colorDim, colorReset, colorCyan, colorReset,
345+
colorCyan, colorReset, colorDim, types.Version, colorReset, colorCyan, colorReset,
346+
colorCyan, colorReset)
347+
}
348+
}
349+
249350
func printVersion() {
250-
fmt.Printf("%s v%s\n", types.AppName, types.Version)
251-
fmt.Printf("HTTP/HTTPS Security Analysis Tool\n")
252-
fmt.Printf("https://github.com/ismailtasdelen/securetrace\n")
351+
if useColors {
352+
fmt.Printf("%s%s%s v%s%s\n", colorBold, colorCyan, types.AppName, types.Version, colorReset)
353+
fmt.Printf("%s🔒 HTTP/HTTPS Security Analysis Tool%s\n", colorDim, colorReset)
354+
fmt.Printf("%s🌐 https://github.com/ismailtasdelen/securetrace%s\n", colorDim, colorReset)
355+
} else {
356+
fmt.Printf("%s v%s\n", types.AppName, types.Version)
357+
fmt.Printf("HTTP/HTTPS Security Analysis Tool\n")
358+
fmt.Printf("https://github.com/ismailtasdelen/securetrace\n")
359+
}
253360
}
254361

255362
func printUsage() {
256-
fmt.Printf(`%s v%s - HTTP/HTTPS Security Analysis Tool
363+
if useColors {
364+
fmt.Printf(`%s%s%s v%s%s - HTTP/HTTPS Security Analysis Tool
365+
366+
%s📋 USAGE:%s
367+
securetrace [OPTIONS] <URL> [URL...]
368+
369+
%s⚡ COMMANDS:%s
370+
version Print version information
371+
372+
%s🔧 OPTIONS:%s
373+
%s-o, --output%s <FORMAT> Output format: text, json, html, csv (default: text)
374+
%s-f, --file%s <PATH> Write output to file instead of stdout
375+
%s-A, --user-agent%s <UA> User agent string or profile (chrome, firefox, safari, curl, wget)
376+
%s-t, --timeout%s <DURATION> Request timeout (default: 30s)
377+
%s-x, --proxy%s <URL> Proxy URL (http, https, or socks5)
378+
%s-c, --concurrency%s <N> Number of concurrent requests (default: 5)
379+
%s-r, --retries%s <N> Number of retries on failure (default: 3)
380+
%s-k, --insecure%s Skip TLS certificate verification
381+
%s--no-redirect%s Don't follow redirects
382+
%s--no-color%s Disable colored output
383+
%s-v, --verbose%s Enable verbose output
384+
%s--config%s <PATH> Configuration file path
385+
%s-h, --help%s Show this help message
386+
387+
%s📖 EXAMPLES:%s
388+
%s# Basic trace%s
389+
securetrace https://example.com
390+
391+
%s# JSON output to file%s
392+
securetrace -o json -f report.json https://example.com
393+
394+
%s# HTML report%s
395+
securetrace -o html -f report.html https://example.com
396+
397+
%s# Multiple URLs with concurrency%s
398+
securetrace -c 10 https://site1.com https://site2.com https://site3.com
399+
400+
%s# Use Chrome user agent with proxy%s
401+
securetrace -A chrome -x http://proxy:8080 https://example.com
402+
403+
%s# Skip certificate verification%s
404+
securetrace -k https://self-signed.example.com
405+
406+
%s🔗 For more information: https://github.com/ismailtasdelen/securetrace%s
407+
`,
408+
colorBold, colorCyan, types.AppName, types.Version, colorReset,
409+
colorBold+colorGreen, colorReset,
410+
colorBold+colorYellow, colorReset,
411+
colorBold+colorBlue, colorReset,
412+
colorCyan, colorReset,
413+
colorCyan, colorReset,
414+
colorCyan, colorReset,
415+
colorCyan, colorReset,
416+
colorCyan, colorReset,
417+
colorCyan, colorReset,
418+
colorCyan, colorReset,
419+
colorCyan, colorReset,
420+
colorCyan, colorReset,
421+
colorCyan, colorReset,
422+
colorCyan, colorReset,
423+
colorCyan, colorReset,
424+
colorCyan, colorReset,
425+
colorBold+colorMagenta, colorReset,
426+
colorDim, colorReset,
427+
colorDim, colorReset,
428+
colorDim, colorReset,
429+
colorDim, colorReset,
430+
colorDim, colorReset,
431+
colorDim, colorReset,
432+
colorDim, colorReset)
433+
} else {
434+
fmt.Printf(`%s v%s - HTTP/HTTPS Security Analysis Tool
257435
258436
USAGE:
259437
securetrace [OPTIONS] <URL> [URL...]
@@ -297,6 +475,7 @@ EXAMPLES:
297475
298476
For more information: https://github.com/ismailtasdelen/securetrace
299477
`, types.AppName, types.Version)
478+
}
300479
}
301480

302481
func isTerminal() bool {
@@ -306,3 +485,68 @@ func isTerminal() bool {
306485
}
307486
return (fileInfo.Mode() & os.ModeCharDevice) != 0
308487
}
488+
489+
func truncateURL(u string, maxLen int) string {
490+
if len(u) <= maxLen {
491+
return u
492+
}
493+
return u[:maxLen-3] + "..."
494+
}
495+
496+
// Spinner provides animated loading indicator
497+
type Spinner struct {
498+
frames []string
499+
message string
500+
running bool
501+
mu sync.Mutex
502+
stopChan chan struct{}
503+
}
504+
505+
// NewSpinner creates a new spinner
506+
func NewSpinner(message string) *Spinner {
507+
return &Spinner{
508+
frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
509+
message: message,
510+
stopChan: make(chan struct{}),
511+
}
512+
}
513+
514+
// SetMessage updates the spinner message
515+
func (s *Spinner) SetMessage(message string) {
516+
s.mu.Lock()
517+
defer s.mu.Unlock()
518+
s.message = message
519+
}
520+
521+
// Start begins the spinner animation
522+
func (s *Spinner) Start() {
523+
s.running = true
524+
go func() {
525+
i := 0
526+
for {
527+
select {
528+
case <-s.stopChan:
529+
return
530+
default:
531+
s.mu.Lock()
532+
if useColors {
533+
fmt.Printf("\r%s%s%s %s", colorCyan, s.frames[i], colorReset, s.message)
534+
} else {
535+
fmt.Printf("\r%s %s", s.frames[i], s.message)
536+
}
537+
s.mu.Unlock()
538+
i = (i + 1) % len(s.frames)
539+
time.Sleep(80 * time.Millisecond)
540+
}
541+
}
542+
}()
543+
}
544+
545+
// Stop stops the spinner
546+
func (s *Spinner) Stop() {
547+
if s.running {
548+
s.running = false
549+
s.stopChan <- struct{}{}
550+
fmt.Print("\r\033[K") // Clear the line
551+
}
552+
}

0 commit comments

Comments
 (0)