Skip to content

Optional colored output #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ You can download the binary for your platform from [Releases](https://github.com
Example:

```shell
HPTS_RELEASE=v1.7.0; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
HPTS_RELEASE=v1.7.1; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
```

Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later):
Expand Down Expand Up @@ -132,36 +132,38 @@ GitHub: https://github.com/shadowy-pycoder/go-http-proxy-to-socks
Usage: gohpts [OPTIONS]
Options:
-h Show this help message and exit.
-D Run as a daemon (provide -logfile to see logs)
-D Run as a daemon (provide -logfile to see logs)
-M value
Transparent proxy mode: [redirect tproxy]
Transparent proxy mode: [redirect tproxy]
-T string
Address of transparent proxy server (no HTTP)
Address of transparent proxy server (no HTTP)
-U string
User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)
User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)
-c string
Path to certificate PEM encoded file
-d Show logs in DEBUG mode
Path to certificate PEM encoded file
-color
Enable colored output for logs in stdout (no effect if log file provided or -j flag specified)
-d Show logs in DEBUG mode
-f string
Path to server configuration file in YAML format
-j Show logs in JSON format
Path to server configuration file in YAML format
-j Show logs in JSON format
-k string
Path to private key PEM encoded file
Path to private key PEM encoded file
-l string
Address of HTTP proxy server (default "127.0.0.1:8080")
Address of HTTP proxy server (default "127.0.0.1:8080")
-logfile string
Log file path (Default: stdout)
Log file path (Default: stdout)
-s string
Address of SOCKS5 proxy server (default "127.0.0.1:1080")
Address of SOCKS5 proxy server (default "127.0.0.1:1080")
-sniff
Enable traffic sniffing for HTTP and TLS
Enable traffic sniffing for HTTP and TLS
-snifflog string
Sniffed traffic log file path (Default: the same as -logfile)
Sniffed traffic log file path (Default: the same as -logfile)
-t string
Address of transparent proxy server (it starts along with HTTP proxy server)
Address of transparent proxy server (it starts along with HTTP proxy server)
-u string
User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal)
-v print version
User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal)
-v print version
```

### Configuration via CLI flags
Expand Down
7 changes: 4 additions & 3 deletions cmd/gohpts/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import (

const (
app string = "gohpts"
addrSOCKS = "127.0.0.1:1080"
addrHTTP = "127.0.0.1:8080"
tproxyOS = "linux"
addrSOCKS string = "127.0.0.1:1080"
addrHTTP string = "127.0.0.1:8080"
tproxyOS string = "linux"
)
const usagePrefix string = `
_____ _ _ _____ _______ _____
Expand Down Expand Up @@ -61,6 +61,7 @@ func root(args []string) error {
flags.BoolVar(&conf.Json, "j", false, "Show logs in JSON format")
flags.BoolVar(&conf.Sniff, "sniff", false, "Enable traffic sniffing for HTTP and TLS")
flags.StringVar(&conf.SniffLogFile, "snifflog", "", "Sniffed traffic log file path (Default: the same as -logfile)")
flags.BoolVar(&conf.Color, "color", false, "Enable colored output for logs in stdout (no effect if log file provided or -j flag specified)")
flags.BoolFunc("v", "print version", func(flagValue string) error {
fmt.Println(gohpts.Version)
os.Exit(0)
Expand Down
40 changes: 14 additions & 26 deletions gohpts.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ func (p *proxyapp) sniffreporter(wg *sync.WaitGroup, sniffheader *[]string, reqC
defer wg.Done()
sniffheaderlen := len(*sniffheader)
for {
req, okreq := <-reqChan // if resp comes first it blocks
req, okreq := <-reqChan // FIX: if resp comes first it blocks
resp, okresp := <-respChan
if !okreq || !okresp {
return
Expand All @@ -543,7 +543,7 @@ func (p *proxyapp) sniffreporter(wg *sync.WaitGroup, sniffheader *[]string, reqC
}
}

func sniff(data []byte, logger *zerolog.Logger) ([]byte, error) {
func sniff(data []byte) ([]byte, error) {
// TODO: check if it is http or tls beforehand
h := &layers.HTTPMessage{}
if err := h.Parse(data); err == nil && !h.IsEmpty() {
Expand All @@ -559,26 +559,13 @@ func sniff(data []byte, logger *zerolog.Logger) ([]byte, error) {
if len(m.Records) > 0 {
hsrec := m.Records[0]
if hsrec.ContentType == layers.HandshakeTLSVal { // TODO: add more cases, parse all records
parser := layers.HSTLSParserByType(hsrec.Data[0])
switch parser.(type) {
case *layers.TLSClientHello:
tc := parser.(*layers.TLSClientHello)
err := tc.ParseHS(hsrec.Data)
switch parser := layers.HSTLSParserByType(hsrec.Data[0]).(type) {
case *layers.TLSClientHello, *layers.TLSServerHello:
err := parser.ParseHS(hsrec.Data)
if err != nil {
return nil, err
}
j, err := json.Marshal(tc)
if err != nil {
return nil, err
}
return j, nil
case *layers.TLSServerHello:
ts := parser.(*layers.TLSServerHello)
err := ts.ParseHS(hsrec.Data)
if err != nil {
return nil, err
}
j, err := json.Marshal(ts)
j, err := json.Marshal(parser)
if err != nil {
return nil, err
}
Expand All @@ -605,7 +592,7 @@ func (p *proxyapp) copyWithTimeout(dst net.Conn, src net.Conn, msgChan chan<- []
break
}
if p.sniff {
s, err := sniff(buf[0:nr], p.logger)
s, err := sniff(buf[0:nr])
if err == nil {
msgChan <- s
}
Expand Down Expand Up @@ -665,7 +652,7 @@ func parseProxyAuth(auth string) (username, password string, ok bool) {
return "", "", false
}
const prefix = "Basic "
if len(auth) < len(prefix) || strings.ToLower(prefix) != strings.ToLower(auth[:len(prefix)]) {
if len(auth) < len(prefix) || !strings.EqualFold(prefix, auth[:len(prefix)]) {
return "", "", false
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
Expand Down Expand Up @@ -797,23 +784,24 @@ type Config struct {
Json bool
Sniff bool
SniffLogFile string
Color bool
}

type logWriter struct {
file *os.File
}

func (writer logWriter) Write(bytes []byte) (int, error) {
return fmt.Fprintf(writer.file, fmt.Sprintf("%s | ERROR | %s", time.Now().Format(time.RFC3339), string(bytes)))
return fmt.Fprintf(writer.file, "%s ERR %s", time.Now().Format(time.RFC3339), string(bytes))
}

type jsonLogWriter struct {
file *os.File
}

func (writer jsonLogWriter) Write(bytes []byte) (int, error) {
return fmt.Fprintf(writer.file, fmt.Sprintf("{\"level\":\"error\",\"time\":\"%s\",\"message\":\"%s\"}\n",
time.Now().Format(time.RFC3339), strings.TrimRight(string(bytes), "\n")))
return fmt.Fprintf(writer.file, "{\"level\":\"error\",\"time\":\"%s\",\"message\":\"%s\"}\n",
time.Now().Format(time.RFC3339), strings.TrimRight(string(bytes), "\n"))
}

type proxyEntry struct {
Expand Down Expand Up @@ -902,8 +890,8 @@ func New(conf *Config) *proxyapp {
log.SetFlags(0)
logWriter := logWriter{file: logfile}
log.SetOutput(logWriter)
noColor := logfile != os.Stdout
sniffNoColor := snifflog != os.Stdout
noColor := !conf.Color || logfile != os.Stdout
sniffNoColor := !conf.Color || snifflog != os.Stdout
output := zerolog.ConsoleWriter{Out: logfile, TimeFormat: time.RFC3339, NoColor: noColor}
logger = zerolog.New(output).With().Timestamp().Logger()
sniffoutput := zerolog.ConsoleWriter{Out: snifflog, TimeFormat: time.RFC3339, NoColor: sniffNoColor}
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package gohpts

const Version string = "gohpts v1.7.0"
const Version string = "gohpts v1.7.1"