Skip to content
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

Add http proxy and socks5 proxy #29

Merged
merged 4 commits into from
Dec 31, 2021
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
18 changes: 11 additions & 7 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ func (c *Client) DoRawWithOptions(method, url, uripath string, headers map[strin
return c.do(method, url, uripath, headers, body, redirectstatus, options)
}

func (c *Client) getConn(protocol, host string, options Options) (Conn, error) {
if options.Proxy != "" {
return c.dialer.DialWithProxy(protocol, host, c.Options.Proxy, c.Options.ProxyDialTimeout)
}
if options.Timeout < 0 {
options.Timeout = 0
}
return c.dialer.DialTimeout(protocol, host, options.Timeout)
}

func (c *Client) do(method, url, uripath string, headers map[string][]string, body io.Reader, redirectstatus *RedirectStatus, options Options) (*http.Response, error) {
protocol := "http"
if strings.HasPrefix(strings.ToLower(url), "https://") {
Expand Down Expand Up @@ -137,13 +147,7 @@ func (c *Client) do(method, url, uripath string, headers map[string][]string, bo
protocol = "https"
}

var conn Conn
if options.Timeout > 0 {
conn, err = c.dialer.DialTimeout(protocol, host, options.Timeout)
} else {
conn, err = c.dialer.Dial(protocol, host)
}

conn, err := c.getConn(protocol, host, options)
if err != nil {
return nil, err
}
Expand Down
70 changes: 56 additions & 14 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ package rawhttp

import (
"crypto/tls"
"fmt"
"io"
"net"
"net/url"
"strings"
"sync"
"time"

"github.com/projectdiscovery/rawhttp/client"
"github.com/projectdiscovery/rawhttp/proxy"
)

// Dialer can dial a remote HTTP server.
type Dialer interface {
// Dial dials a remote http server returning a Conn.
Dial(protocol, addr string) (Conn, error)
DialWithProxy(protocol, addr, proxyURL string, timeout time.Duration) (Conn, error)
// Dial dials a remote http server with timeout returning a Conn.
DialTimeout(protocol, addr string, timeout time.Duration) (Conn, error)
}
Expand Down Expand Up @@ -53,25 +58,62 @@ func (d *dialer) dialTimeout(protocol, addr string, timeout time.Duration) (Conn
}, err
}

func clientDial(protocol, addr string, timeout time.Duration) (net.Conn, error) {
// http
if protocol == "http" {
if timeout > 0 {
return net.DialTimeout("tcp", addr, timeout)
func (d *dialer) DialWithProxy(protocol, addr, proxyURL string, timeout time.Duration) (Conn, error) {
var c net.Conn
u, err := url.Parse(proxyURL)
if err != nil {
return nil, fmt.Errorf("unsupported proxy error: %w", err)
}
switch u.Scheme {
case "http":
c, err = proxy.HTTPDialer(proxyURL, timeout)(addr)
case "socks5", "socks5h":
c, err = proxy.Socks5Dialer(proxyURL, timeout)(addr)
default:
return nil, fmt.Errorf("unsupported proxy protocol: %s", proxyURL)
}
if err != nil {
return nil, fmt.Errorf("proxy error: %w", err)
}
if protocol == "https" {
if c, err = TlsHandshake(c, addr); err != nil {
return nil, fmt.Errorf("tls handshake error: %w", err)
}
return net.Dial("tcp", addr)
}
return &conn{
Client: client.NewClient(c),
Conn: c,
dialer: d,
}, err
}

// https
if timeout > 0 {
conn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
return nil, err
func clientDial(protocol, addr string, timeout time.Duration) (net.Conn, error) {
conn, err := net.DialTimeout("tcp", addr, timeout)
if protocol == "https" {
if conn, err = TlsHandshake(conn, addr); err != nil {
return nil, fmt.Errorf("tls handshake error: %w", err)
}
tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
return tlsConn, tlsConn.Handshake()
}
return tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true})
return conn, err
}

// TlsHandshake tls handshake on a plain connection
func TlsHandshake(conn net.Conn, addr string) (net.Conn, error) {
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]

tlsConn := tls.Client(conn, &tls.Config{
InsecureSkipVerify: true,
ServerName: hostname,
})
if err := tlsConn.Handshake(); err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
}

// Conn is an interface implemented by a connection
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ require (
github.com/projectdiscovery/retryablehttp-go v1.0.1
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9
github.com/remeh/sizedwaitgroup v1.0.0
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNC
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
Expand Down
2 changes: 2 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type Options struct {
AutomaticContentLength bool
CustomHeaders client.Headers
CustomRawBytes []byte
Proxy string
ProxyDialTimeout time.Duration
}

// DefaultOptions is the default configuration options for the client
Expand Down
65 changes: 65 additions & 0 deletions proxy/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package proxy

import (
"encoding/base64"
"fmt"
"net"
"net/url"
"strings"
"time"

"github.com/projectdiscovery/rawhttp/client"
)

func HTTPDialer(proxyAddr string, timeout time.Duration) DialFunc {
return func(addr string) (net.Conn, error) {
var netConn net.Conn
var err error
var auth string
// close the connection when an error occurs
defer func() {
if err != nil && netConn != nil {
netConn.Close()
}
}()
u, err := url.Parse(proxyAddr)
if err != nil {
return nil, err
}
if strings.Contains(proxyAddr, "@") {
split := strings.Split(proxyAddr, "@")
auth = base64.StdEncoding.EncodeToString([]byte(split[0]))
proxyAddr = split[1]
}
if timeout == 0 {
netConn, err = net.Dial("tcp", u.Host)
} else {
netConn, err = net.DialTimeout("tcp", u.Host, timeout)
}
if err != nil {
return nil, err
}
conn := client.NewClient(netConn)

req := "CONNECT " + addr + " HTTP/1.1\r\n"
if auth != "" {
req += "Proxy-Authorization: Basic " + auth + "\r\n"
}
req += "\r\n"
clientReq := &client.Request {
RawBytes: []byte(req),
}
if err = conn.WriteRequest(clientReq); err != nil {
return nil, err
}
resp, err := conn.ReadResponse()
if err != nil {
return nil, err
}
if resp.Status.Code != 200 {
return nil, fmt.Errorf("could not connect to proxy: %s status code: %d", proxyAddr, resp.Status.Code)
}

return netConn, nil
}
}
7 changes: 7 additions & 0 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package proxy

import (
"net"
)

type DialFunc func(addr string) (net.Conn, error)
26 changes: 26 additions & 0 deletions proxy/socks5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package proxy

import (
"net"
"net/url"
"time"

"golang.org/x/net/proxy"
)

func Socks5Dialer(proxyAddr string, timeout time.Duration) DialFunc {
var (
u *url.URL
err error
dialer proxy.Dialer
)
if u, err = url.Parse(proxyAddr); err == nil {
dialer, err = proxy.FromURL(u, proxy.Direct)
}
return func(addr string) (net.Conn, error) {
if err != nil {
return nil, err
}
return dialer.Dial("tcp", addr)
}
}