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
2036var (
2137 cfgFile string
@@ -30,26 +46,32 @@ var (
3046 noColor bool
3147 concurrency int
3248 retries int
49+ useColors bool
3350)
3451
3552func 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
4263func 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 ("\n Interrupted, cleaning up..." )
233+ if useColors {
234+ fmt .Printf ("\n %s⚠ Interrupted, cleaning up...%s\n " , colorYellow , colorReset )
235+ } else {
236+ fmt .Println ("\n Interrupted, 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+
249350func 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
255362func 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
258436USAGE:
259437 securetrace [OPTIONS] <URL> [URL...]
@@ -297,6 +475,7 @@ EXAMPLES:
297475
298476For more information: https://github.com/ismailtasdelen/securetrace
299477` , types .AppName , types .Version )
478+ }
300479}
301480
302481func 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