package  http 
 
import  ( 
	"context"  
	"errors"  
	"io"  
	"net"  
	"strconv"  
	"time"  
) 
 
var  ( 
	socksnoDeadline    = time .Time {} 
	socksaLongTimeAgo  = time .Unix (1 , 0 ) 
) 
 
func  (d  *socksDialer ) connect  (ctx  context .Context , c  net .Conn , address  string ) (_  net .Addr , ctxErr  error ) { 
	host , port , err  := sockssplitHostPort (address ) 
	if  err  != nil  { 
		return  nil , err  
	} 
	if  deadline , ok  := ctx .Deadline (); ok  && !deadline .IsZero () { 
		c .SetDeadline (deadline ) 
		defer  c .SetDeadline (socksnoDeadline ) 
	} 
	if  ctx  != context .Background () { 
		errCh  := make (chan  error , 1 ) 
		done  := make (chan  struct {}) 
		defer  func () { 
			close (done ) 
			if  ctxErr  == nil  { 
				ctxErr  = <-errCh  
			} 
		}() 
		go  func () { 
			select   { 
			case  <- ctx .Done (): 
				c .SetDeadline (socksaLongTimeAgo ) 
				errCh  <- ctx .Err () 
			case  <- done : 
				errCh  <- nil  
			} 
		}() 
	} 
 
	b  := make ([]byte , 0 , 6 +len (host ))  
	b  = append (b , socksVersion5 ) 
	if  len (d .AuthMethods ) == 0  || d .Authenticate  == nil  { 
		b  = append (b , 1 , byte (socksAuthMethodNotRequired )) 
	} else  { 
		ams  := d .AuthMethods  
		if  len (ams ) > 255  { 
			return  nil , errors .New ("too many authentication methods" ) 
		} 
		b  = append (b , byte (len (ams ))) 
		for  _ , am  := range  ams  { 
			b  = append (b , byte (am )) 
		} 
	} 
	if  _, ctxErr  = c .Write (b ); ctxErr  != nil  { 
		return  
	} 
 
	if  _, ctxErr  = io .ReadFull (c , b [:2 ]); ctxErr  != nil  { 
		return  
	} 
	if  b [0 ] != socksVersion5  { 
		return  nil , errors .New ("unexpected protocol version "  + strconv .Itoa (int (b [0 ]))) 
	} 
	am  := socksAuthMethod (b [1 ]) 
	if  am  == socksAuthMethodNoAcceptableMethods  { 
		return  nil , errors .New ("no acceptable authentication methods" ) 
	} 
	if  d .Authenticate  != nil  { 
		if  ctxErr  = d .Authenticate (ctx , c , am ); ctxErr  != nil  { 
			return  
		} 
	} 
 
	b  = b [:0 ] 
	b  = append (b , socksVersion5 , byte (d .cmd ), 0 ) 
	if  ip  := net .ParseIP (host ); ip  != nil  { 
		if  ip4  := ip .To4 (); ip4  != nil  { 
			b  = append (b , socksAddrTypeIPv4 ) 
			b  = append (b , ip4 ...) 
		} else  if  ip6  := ip .To16 (); ip6  != nil  { 
			b  = append (b , socksAddrTypeIPv6 ) 
			b  = append (b , ip6 ...) 
		} else  { 
			return  nil , errors .New ("unknown address type" ) 
		} 
	} else  { 
		if  len (host ) > 255  { 
			return  nil , errors .New ("FQDN too long" ) 
		} 
		b  = append (b , socksAddrTypeFQDN ) 
		b  = append (b , byte (len (host ))) 
		b  = append (b , host ...) 
	} 
	b  = append (b , byte (port >>8 ), byte (port )) 
	if  _, ctxErr  = c .Write (b ); ctxErr  != nil  { 
		return  
	} 
 
	if  _, ctxErr  = io .ReadFull (c , b [:4 ]); ctxErr  != nil  { 
		return  
	} 
	if  b [0 ] != socksVersion5  { 
		return  nil , errors .New ("unexpected protocol version "  + strconv .Itoa (int (b [0 ]))) 
	} 
	if  cmdErr  := socksReply (b [1 ]); cmdErr  != socksStatusSucceeded  { 
		return  nil , errors .New ("unknown error "  + cmdErr .String ()) 
	} 
	if  b [2 ] != 0  { 
		return  nil , errors .New ("non-zero reserved field" ) 
	} 
	l  := 2  
	var  a  socksAddr  
	switch  b [3 ] { 
	case  socksAddrTypeIPv4 : 
		l  += net .IPv4len  
		a .IP  = make (net .IP , net .IPv4len ) 
	case  socksAddrTypeIPv6 : 
		l  += net .IPv6len  
		a .IP  = make (net .IP , net .IPv6len ) 
	case  socksAddrTypeFQDN : 
		if  _ , err  := io .ReadFull (c , b [:1 ]); err  != nil  { 
			return  nil , err  
		} 
		l  += int (b [0 ]) 
	default : 
		return  nil , errors .New ("unknown address type "  + strconv .Itoa (int (b [3 ]))) 
	} 
	if  cap (b ) < l  { 
		b  = make ([]byte , l ) 
	} else  { 
		b  = b [:l ] 
	} 
	if  _, ctxErr  = io .ReadFull (c , b ); ctxErr  != nil  { 
		return  
	} 
	if  a .IP  != nil  { 
		copy (a .IP , b ) 
	} else  { 
		a .Name  = string (b [:len (b )-2 ]) 
	} 
	a .Port  = int (b [len (b )-2 ])<<8  | int (b [len (b )-1 ]) 
	return  &a , nil  
} 
 
func  sockssplitHostPort  (address  string ) (string , int , error ) { 
	host , port , err  := net .SplitHostPort (address ) 
	if  err  != nil  { 
		return  "" , 0 , err  
	} 
	portnum , err  := strconv .Atoi (port ) 
	if  err  != nil  { 
		return  "" , 0 , err  
	} 
	if  1  > portnum  || portnum  > 0xffff  { 
		return  "" , 0 , errors .New ("port number out of range "  + port ) 
	} 
	return  host , portnum , nil  
} 
 
 
type  socksCommand  int  
 
func  (cmd  socksCommand ) String  () string  { 
	switch  cmd  { 
	case  socksCmdConnect : 
		return  "socks connect"  
	case  sockscmdBind : 
		return  "socks bind"  
	default : 
		return  "socks "  + strconv .Itoa (int (cmd )) 
	} 
} 
 
 
type  socksAuthMethod  int  
 
 
type  socksReply  int  
 
func  (code  socksReply ) String  () string  { 
	switch  code  { 
	case  socksStatusSucceeded : 
		return  "succeeded"  
	case  0x01 : 
		return  "general SOCKS server failure"  
	case  0x02 : 
		return  "connection not allowed by ruleset"  
	case  0x03 : 
		return  "network unreachable"  
	case  0x04 : 
		return  "host unreachable"  
	case  0x05 : 
		return  "connection refused"  
	case  0x06 : 
		return  "TTL expired"  
	case  0x07 : 
		return  "command not supported"  
	case  0x08 : 
		return  "address type not supported"  
	default : 
		return  "unknown code: "  + strconv .Itoa (int (code )) 
	} 
} 
 
 
const  ( 
	socksVersion5  = 0x05  
 
	socksAddrTypeIPv4  = 0x01  
	socksAddrTypeFQDN  = 0x03  
	socksAddrTypeIPv6  = 0x04  
 
	socksCmdConnect  socksCommand  = 0x01   
	sockscmdBind     socksCommand  = 0x02   
 
	socksAuthMethodNotRequired          socksAuthMethod  = 0x00   
	socksAuthMethodUsernamePassword     socksAuthMethod  = 0x02   
	socksAuthMethodNoAcceptableMethods  socksAuthMethod  = 0xff   
 
	socksStatusSucceeded  socksReply  = 0x00  
) 
 
 
 
type  socksAddr  struct  { 
	Name  string   
	IP    net .IP  
	Port  int  
} 
 
func  (a  *socksAddr ) Network  () string  { return  "socks"  } 
 
func  (a  *socksAddr ) String  () string  { 
	if  a  == nil  { 
		return  "<nil>"  
	} 
	port  := strconv .Itoa (a .Port ) 
	if  a .IP  == nil  { 
		return  net .JoinHostPort (a .Name , port ) 
	} 
	return  net .JoinHostPort (a .IP .String (), port ) 
} 
 
 
type  socksConn  struct  { 
	net .Conn  
 
	boundAddr  net .Addr  
} 
 
 
 
func  (c  *socksConn ) BoundAddr  () net .Addr  { 
	if  c  == nil  { 
		return  nil  
	} 
	return  c .boundAddr  
} 
 
 
type  socksDialer  struct  { 
	cmd           socksCommand   
	proxyNetwork  string         
	proxyAddress  string         
 
	 
 
	ProxyDial  func (context .Context , string , string ) (net .Conn , error ) 
 
	 
 
 
	AuthMethods  []socksAuthMethod  
 
	 
 
 
	Authenticate  func (context .Context , io .ReadWriter , socksAuthMethod ) error  
} 
 
 
 
 
 
 
 
 
 
 
 
func  (d  *socksDialer ) DialContext  (ctx  context .Context , network , address  string ) (net .Conn , error ) { 
	if  err  := d .validateTarget (network , address ); err  != nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	if  ctx  == nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : errors .New ("nil context" )} 
	} 
	var  err  error  
	var  c  net .Conn  
	if  d .ProxyDial  != nil  { 
		c , err  = d .ProxyDial (ctx , d .proxyNetwork , d .proxyAddress ) 
	} else  { 
		var  dd  net .Dialer  
		c , err  = dd .DialContext (ctx , d .proxyNetwork , d .proxyAddress ) 
	} 
	if  err  != nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	a , err  := d .connect (ctx , c , address ) 
	if  err  != nil  { 
		c .Close () 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	return  &socksConn {Conn : c , boundAddr : a }, nil  
} 
 
 
 
 
 
 
 
func  (d  *socksDialer ) DialWithConn  (ctx  context .Context , c  net .Conn , network , address  string ) (net .Addr , error ) { 
	if  err  := d .validateTarget (network , address ); err  != nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	if  ctx  == nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : errors .New ("nil context" )} 
	} 
	a , err  := d .connect (ctx , c , address ) 
	if  err  != nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	return  a , nil  
} 
 
 
 
 
 
 
 
func  (d  *socksDialer ) Dial  (network , address  string ) (net .Conn , error ) { 
	if  err  := d .validateTarget (network , address ); err  != nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	var  err  error  
	var  c  net .Conn  
	if  d .ProxyDial  != nil  { 
		c , err  = d .ProxyDial (context .Background (), d .proxyNetwork , d .proxyAddress ) 
	} else  { 
		c , err  = net .Dial (d .proxyNetwork , d .proxyAddress ) 
	} 
	if  err  != nil  { 
		proxy , dst , _  := d .pathAddrs (address ) 
		return  nil , &net .OpError {Op : d .cmd .String (), Net : network , Source : proxy , Addr : dst , Err : err } 
	} 
	if  _ , err  := d .DialWithConn (context .Background (), c , network , address ); err  != nil  { 
		c .Close () 
		return  nil , err  
	} 
	return  c , nil  
} 
 
func  (d  *socksDialer ) validateTarget  (network , address  string ) error  { 
	switch  network  { 
	case  "tcp" , "tcp6" , "tcp4" : 
	default : 
		return  errors .New ("network not implemented" ) 
	} 
	switch  d .cmd  { 
	case  socksCmdConnect , sockscmdBind : 
	default : 
		return  errors .New ("command not implemented" ) 
	} 
	return  nil  
} 
 
func  (d  *socksDialer ) pathAddrs  (address  string ) (proxy , dst  net .Addr , err  error ) { 
	for  i , s  := range  []string {d .proxyAddress , address } { 
		host , port , err  := sockssplitHostPort (s ) 
		if  err  != nil  { 
			return  nil , nil , err  
		} 
		a  := &socksAddr {Port : port } 
		a .IP  = net .ParseIP (host ) 
		if  a .IP  == nil  { 
			a .Name  = host  
		} 
		if  i  == 0  { 
			proxy  = a  
		} else  { 
			dst  = a  
		} 
	} 
	return  
} 
 
 
 
func  socksNewDialer  (network , address  string ) *socksDialer  { 
	return  &socksDialer {proxyNetwork : network , proxyAddress : address , cmd : socksCmdConnect } 
} 
 
const  ( 
	socksauthUsernamePasswordVersion  = 0x01  
	socksauthStatusSucceeded          = 0x00  
) 
 
 
 
type  socksUsernamePassword  struct  { 
	Username  string  
	Password  string  
} 
 
 
 
func  (up  *socksUsernamePassword ) Authenticate  (ctx  context .Context , rw  io .ReadWriter , auth  socksAuthMethod ) error  { 
	switch  auth  { 
	case  socksAuthMethodNotRequired : 
		return  nil  
	case  socksAuthMethodUsernamePassword : 
		if  len (up .Username ) == 0  || len (up .Username ) > 255  || len (up .Password ) > 255  { 
			return  errors .New ("invalid username/password" ) 
		} 
		b  := []byte {socksauthUsernamePasswordVersion } 
		b  = append (b , byte (len (up .Username ))) 
		b  = append (b , up .Username ...) 
		b  = append (b , byte (len (up .Password ))) 
		b  = append (b , up .Password ...) 
		 
 
		if  _ , err  := rw .Write (b ); err  != nil  { 
			return  err  
		} 
		if  _ , err  := io .ReadFull (rw , b [:2 ]); err  != nil  { 
			return  err  
		} 
		if  b [0 ] != socksauthUsernamePasswordVersion  { 
			return  errors .New ("invalid username/password version" ) 
		} 
		if  b [1 ] != socksauthStatusSucceeded  { 
			return  errors .New ("username/password authentication failed" ) 
		} 
		return  nil  
	} 
	return  errors .New ("unsupported authentication method "  + strconv .Itoa (int (auth ))) 
} 
  
The pages are generated with Golds   v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds  is a Go 101  project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1  (reachable from the left QR code) to get the latest news of Golds .