3
0
mirror of https://github.com/ergochat/ergo.git synced 2025-01-03 08:32:43 +01:00

Merge pull request #979 from slingamn/websockets_draft.7

websockets implementation
This commit is contained in:
Shivaram Lingamneni 2020-05-05 17:54:52 -07:00 committed by GitHub
commit d37af694af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 4566 additions and 287 deletions

View File

@ -40,6 +40,13 @@ server:
# "/hidden_service_sockets/oragono_tor_sock": # "/hidden_service_sockets/oragono_tor_sock":
# tor: true # tor: true
# Example of a WebSocket listener:
# ":4430":
# websocket: true
# tls:
# key: tls.key
# cert: tls.crt
# sets the permissions for Unix listen sockets. on a typical Linux system, # sets the permissions for Unix listen sockets. on a typical Linux system,
# the default is 0775 or 0755, which prevents other users/groups from connecting # the default is 0775 or 0755, which prevents other users/groups from connecting
# to the socket. With 0777, it behaves like a normal TCP socket # to the socket. With 0777, it behaves like a normal TCP socket
@ -81,6 +88,16 @@ server:
# should clients include this STS policy when they ship their inbuilt preload lists? # should clients include this STS policy when they ship their inbuilt preload lists?
preload: false preload: false
websockets:
# Restrict the origin of WebSocket connections by matching the "Origin" HTTP
# header. This settings makes oragono reject every WebSocket connection,
# except when it originates from one of the hosts in this list. Use this to
# prevent malicious websites from making their visitors connect to oragono
# without their knowledge. An empty list means that there are no restrictions.
allowed-origins:
# - "https://oragono.io"
# - "https://*.oragono.io"
# casemapping controls what kinds of strings are permitted as identifiers (nicknames, # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
# channel names, account names, etc.), and how they are normalized for case. # channel names, account names, etc.), and how they are normalized for case.
# with the recommended default of 'precis', utf-8 identifiers that are "sane" # with the recommended default of 'precis', utf-8 identifiers that are "sane"

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/go-ldap/ldap/v3 v3.1.7 github.com/go-ldap/ldap/v3 v3.1.7
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/gorilla/websocket v1.4.2
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 // indirect github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 // indirect
github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac
github.com/onsi/ginkgo v1.12.0 // indirect github.com/onsi/ginkgo v1.12.0 // indirect

2
go.sum
View File

@ -19,6 +19,8 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 h1:KmRLPRstEJiE/9OjumKqI8Rccip8Qmyw2FwyTFxtVqs= github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 h1:KmRLPRstEJiE/9OjumKqI8Rccip8Qmyw2FwyTFxtVqs=
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940/go.mod h1:VOmrX6cmj7zwUeexC9HzznUdTIObHqIXUrWNYS+Ik7w= github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940/go.mod h1:VOmrX6cmj7zwUeexC9HzznUdTIObHqIXUrWNYS+Ik7w=
github.com/goshuirc/irc-go v0.0.0-20190713001546-05ecc95249a0 h1:unxsR0de0MIS708eZI7lKa6HiP8FS0PhGCWwwEt9+vQ= github.com/goshuirc/irc-go v0.0.0-20190713001546-05ecc95249a0 h1:unxsR0de0MIS708eZI7lKa6HiP8FS0PhGCWwwEt9+vQ=

View File

@ -254,26 +254,31 @@ type ClientDetails struct {
} }
// RunClient sets up a new client and runs its goroutine. // RunClient sets up a new client and runs its goroutine.
func (server *Server) RunClient(conn clientConn, proxyLine string) { func (server *Server) RunClient(conn IRCConn) {
proxiedConn := conn.UnderlyingConn()
var isBanned bool var isBanned bool
var banMsg string var banMsg string
var realIP net.IP realIP := utils.AddrToIP(proxiedConn.RemoteAddr())
if conn.Config.Tor { var proxiedIP net.IP
realIP = utils.IPv4LoopbackAddress if proxiedConn.Config.Tor {
// cover up details of the tor proxying infrastructure (not a user privacy concern,
// but a hardening measure):
proxiedIP = utils.IPv4LoopbackAddress
isBanned, banMsg = server.checkTorLimits() isBanned, banMsg = server.checkTorLimits()
} else { } else {
realIP = utils.AddrToIP(conn.Conn.RemoteAddr()) ipToCheck := realIP
// skip the ban check for k8s-style proxy-before-TLS if proxiedConn.ProxiedIP != nil {
if proxyLine == "" { proxiedIP = proxiedConn.ProxiedIP
isBanned, banMsg = server.checkBans(realIP) ipToCheck = proxiedIP
} }
isBanned, banMsg = server.checkBans(ipToCheck)
} }
if isBanned { if isBanned {
// this might not show up properly on some clients, // this might not show up properly on some clients,
// but our objective here is just to close the connection out before it has a load impact on us // but our objective here is just to close the connection out before it has a load impact on us
conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg))) conn.WriteLine([]byte(fmt.Sprintf(errorMsg, banMsg)))
conn.Conn.Close() conn.Close()
return return
} }
@ -282,13 +287,13 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
now := time.Now().UTC() now := time.Now().UTC()
config := server.Config() config := server.Config()
// give them 1k of grace over the limit: // give them 1k of grace over the limit:
socket := NewSocket(conn.Conn, ircmsg.MaxlenTagsFromClient+512+1024, config.Server.MaxSendQBytes) socket := NewSocket(conn, config.Server.MaxSendQBytes)
client := &Client{ client := &Client{
lastSeen: now, lastSeen: now,
lastActive: now, lastActive: now,
channels: make(ChannelSet), channels: make(ChannelSet),
ctime: now, ctime: now,
isSTSOnly: conn.Config.STSOnly, isSTSOnly: proxiedConn.Config.STSOnly,
languages: server.Languages().Default(), languages: server.Languages().Default(),
loginThrottle: connection_limits.GenericThrottle{ loginThrottle: connection_limits.GenericThrottle{
Duration: config.Accounts.LoginThrottling.Duration, Duration: config.Accounts.LoginThrottling.Duration,
@ -299,6 +304,8 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
nick: "*", // * is used until actual nick is given nick: "*", // * is used until actual nick is given
nickCasefolded: "*", nickCasefolded: "*",
nickMaskString: "*", // * is used until actual nick is given nickMaskString: "*", // * is used until actual nick is given
realIP: realIP,
proxiedIP: proxiedIP,
} }
client.writerSemaphore.Initialize(1) client.writerSemaphore.Initialize(1)
client.history.Initialize(config.History.ClientLength, config.History.AutoresizeWindow) client.history.Initialize(config.History.ClientLength, config.History.AutoresizeWindow)
@ -311,7 +318,8 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
ctime: now, ctime: now,
lastActive: now, lastActive: now,
realIP: realIP, realIP: realIP,
isTor: conn.Config.Tor, proxiedIP: proxiedIP,
isTor: proxiedConn.Config.Tor,
} }
client.sessions = []*Session{session} client.sessions = []*Session{session}
@ -322,34 +330,28 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
client.SetMode(defaultMode, true) client.SetMode(defaultMode, true)
} }
if conn.Config.TLSConfig != nil { if proxiedConn.Config.TLSConfig != nil {
client.SetMode(modes.TLS, true) client.SetMode(modes.TLS, true)
// error is not useful to us here anyways so we can ignore it // error is not useful to us here anyways so we can ignore it
session.certfp, _ = socket.CertFP() session.certfp, _ = utils.GetCertFP(proxiedConn.Conn, RegisterTimeout)
} }
if conn.Config.Tor { if session.isTor {
client.SetMode(modes.TLS, true) client.SetMode(modes.TLS, true)
// cover up details of the tor proxying infrastructure (not a user privacy concern,
// but a hardening measure):
session.proxiedIP = utils.IPv4LoopbackAddress
client.proxiedIP = session.proxiedIP
session.rawHostname = config.Server.TorListeners.Vhost session.rawHostname = config.Server.TorListeners.Vhost
client.rawHostname = session.rawHostname client.rawHostname = session.rawHostname
} else { } else {
remoteAddr := conn.Conn.RemoteAddr()
if realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) { if realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) {
// treat local connections as secure (may be overridden later by WEBIRC) // treat local connections as secure (may be overridden later by WEBIRC)
client.SetMode(modes.TLS, true) client.SetMode(modes.TLS, true)
} }
if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) { if config.Server.CheckIdent {
client.doIdentLookup(conn.Conn) client.doIdentLookup(proxiedConn.Conn)
} }
} }
client.realIP = session.realIP
server.stats.Add() server.stats.Add()
client.run(session, proxyLine) client.run(session)
} }
func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) { func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) {
@ -476,21 +478,19 @@ func (client *Client) lookupHostname(session *Session, overwrite bool) {
} }
func (client *Client) doIdentLookup(conn net.Conn) { func (client *Client) doIdentLookup(conn net.Conn) {
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) localTCPAddr, ok := conn.LocalAddr().(*net.TCPAddr)
if err != nil { if !ok {
client.server.logger.Error("internal", "bad server address", err.Error())
return return
} }
serverPort, _ := strconv.Atoi(serverPortString) serverPort := localTCPAddr.Port
clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String()) remoteTCPAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
if err != nil { if !ok {
client.server.logger.Error("internal", "bad client address", err.Error())
return return
} }
clientPort, _ := strconv.Atoi(clientPortString) clientPort := remoteTCPAddr.Port
client.Notice(client.t("*** Looking up your username")) client.Notice(client.t("*** Looking up your username"))
resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds) resp, err := ident.Query(remoteTCPAddr.IP.String(), serverPort, clientPort, IdentTimeoutSeconds)
if err == nil { if err == nil {
err := client.SetNames(resp.Identifier, "", true) err := client.SetNames(resp.Identifier, "", true)
if err == nil { if err == nil {
@ -567,7 +567,7 @@ func (client *Client) t(originalString string) string {
// main client goroutine: read lines and execute the corresponding commands // main client goroutine: read lines and execute the corresponding commands
// `proxyLine` is the PROXY-before-TLS line, if there was one // `proxyLine` is the PROXY-before-TLS line, if there was one
func (client *Client) run(session *Session, proxyLine string) { func (client *Client) run(session *Session) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -601,14 +601,7 @@ func (client *Client) run(session *Session, proxyLine string) {
firstLine := !isReattach firstLine := !isReattach
for { for {
var line string line, err := session.socket.Read()
var err error
if proxyLine == "" {
line, err = session.socket.Read()
} else {
line = proxyLine // pretend we're just now receiving the proxy-before-TLS line
proxyLine = ""
}
if err != nil { if err != nil {
quitMessage := "connection closed" quitMessage := "connection closed"
if err == errReadQ { if err == errReadQ {
@ -681,7 +674,7 @@ func (client *Client) run(session *Session, proxyLine string) {
break break
} else if session.client != client { } else if session.client != client {
// bouncer reattach // bouncer reattach
go session.client.run(session, "") go session.client.run(session)
break break
} }
} }

View File

@ -50,18 +50,10 @@ type TLSListenConfig struct {
// This is the YAML-deserializable type of the value of the `Server.Listeners` map // This is the YAML-deserializable type of the value of the `Server.Listeners` map
type listenerConfigBlock struct { type listenerConfigBlock struct {
TLS TLSListenConfig TLS TLSListenConfig
Tor bool Tor bool
STSOnly bool `yaml:"sts-only"` STSOnly bool `yaml:"sts-only"`
} WebSocket bool
// listenerConfig is the config governing a particular listener (bound address),
// in particular whether it has TLS or Tor (or both) enabled.
type listenerConfig struct {
TLSConfig *tls.Config
Tor bool
STSOnly bool
ProxyBeforeTLS bool
} }
type PersistentStatus uint type PersistentStatus uint
@ -486,8 +478,12 @@ type Config struct {
Listeners map[string]listenerConfigBlock Listeners map[string]listenerConfigBlock
UnixBindMode os.FileMode `yaml:"unix-bind-mode"` UnixBindMode os.FileMode `yaml:"unix-bind-mode"`
TorListeners TorListenersConfig `yaml:"tor-listeners"` TorListeners TorListenersConfig `yaml:"tor-listeners"`
WebSockets struct {
AllowedOrigins []string `yaml:"allowed-origins"`
allowedOriginRegexps []*regexp.Regexp
}
// they get parsed into this internal representation: // they get parsed into this internal representation:
trueListeners map[string]listenerConfig trueListeners map[string]utils.ListenerConfig
STS STSConfig STS STSConfig
LookupHostnames *bool `yaml:"lookup-hostnames"` LookupHostnames *bool `yaml:"lookup-hostnames"`
lookupHostnames bool lookupHostnames bool
@ -747,14 +743,22 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error
return operators, nil return operators, nil
} }
func loadTlsConfig(config TLSListenConfig) (tlsConfig *tls.Config, err error) { func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Config, err error) {
cert, err := tls.LoadX509KeyPair(config.Cert, config.Key) cert, err := tls.LoadX509KeyPair(config.Cert, config.Key)
if err != nil { if err != nil {
return nil, ErrInvalidCertKeyPair return nil, ErrInvalidCertKeyPair
} }
clientAuth := tls.RequestClientCert
if webSocket {
// if Chrome receives a server request for a client certificate
// on a websocket connection, it will immediately disconnect:
// https://bugs.chromium.org/p/chromium/issues/detail?id=329884
// work around this behavior:
clientAuth = tls.NoClientCert
}
result := tls.Config{ result := tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert, ClientAuth: clientAuth,
} }
return &result, nil return &result, nil
} }
@ -765,22 +769,24 @@ func (conf *Config) prepareListeners() (err error) {
return fmt.Errorf("No listeners were configured") return fmt.Errorf("No listeners were configured")
} }
conf.Server.trueListeners = make(map[string]listenerConfig) conf.Server.trueListeners = make(map[string]utils.ListenerConfig)
for addr, block := range conf.Server.Listeners { for addr, block := range conf.Server.Listeners {
var lconf listenerConfig var lconf utils.ListenerConfig
lconf.ProxyDeadline = RegisterTimeout
lconf.Tor = block.Tor lconf.Tor = block.Tor
lconf.STSOnly = block.STSOnly lconf.STSOnly = block.STSOnly
if lconf.STSOnly && !conf.Server.STS.Enabled { if lconf.STSOnly && !conf.Server.STS.Enabled {
return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr) return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr)
} }
if block.TLS.Cert != "" { if block.TLS.Cert != "" {
tlsConfig, err := loadTlsConfig(block.TLS) tlsConfig, err := loadTlsConfig(block.TLS, block.WebSocket)
if err != nil { if err != nil {
return err return err
} }
lconf.TLSConfig = tlsConfig lconf.TLSConfig = tlsConfig
lconf.ProxyBeforeTLS = block.TLS.Proxy lconf.RequireProxy = block.TLS.Proxy
} }
lconf.WebSocket = block.WebSocket
conf.Server.trueListeners[addr] = lconf conf.Server.trueListeners[addr] = lconf
} }
return nil return nil
@ -846,6 +852,14 @@ func LoadConfig(filename string) (config *Config, err error) {
return nil, fmt.Errorf("failed to prepare listeners: %v", err) return nil, fmt.Errorf("failed to prepare listeners: %v", err)
} }
for _, glob := range config.Server.WebSockets.AllowedOrigins {
globre, err := utils.CompileGlob(glob)
if err != nil {
return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob)
}
config.Server.WebSockets.allowedOriginRegexps = append(config.Server.WebSockets.allowedOriginRegexps, globre)
}
if config.Server.STS.Enabled { if config.Server.STS.Enabled {
if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 { if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port) return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
@ -1203,19 +1217,19 @@ func (config *Config) Diff(oldConfig *Config) (addedCaps, removedCaps *caps.Set)
} }
func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) { func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) {
starIndex := strings.IndexByte(guestFormat, '*') if strings.Count(guestFormat, "?") != 0 || strings.Count(guestFormat, "*") != 1 {
if starIndex == -1 { err = errors.New("guest format must contain 1 '*' and no '?'s")
return nil, nil, errors.New("guest format must contain exactly one *") return
} }
initial := guestFormat[:starIndex]
final := guestFormat[starIndex+1:] standard, err = utils.CompileGlob(guestFormat)
if strings.IndexByte(final, '*') != -1 {
return nil, nil, errors.New("guest format must contain exactly one *")
}
standard, err = regexp.Compile(fmt.Sprintf("^%s(.*)%s$", initial, final))
if err != nil { if err != nil {
return return
} }
starIndex := strings.IndexByte(guestFormat, '*')
initial := guestFormat[:starIndex]
final := guestFormat[starIndex+1:]
initialFolded, err := casefoldWithSetting(initial, casemapping) initialFolded, err := casefoldWithSetting(initial, casemapping)
if err != nil { if err != nil {
return return
@ -1224,6 +1238,6 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
if err != nil { if err != nil {
return return
} }
folded, err = regexp.Compile(fmt.Sprintf("^%s(.*)%s$", initialFolded, finalFolded)) folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded))
return return
} }

View File

@ -7,10 +7,7 @@ package irc
import ( import (
"errors" "errors"
"fmt"
"net" "net"
"strings"
"time"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/utils" "github.com/oragono/oragono/irc/utils"
@ -58,7 +55,7 @@ func (wc *webircConfig) Populate() (err error) {
} }
// ApplyProxiedIP applies the given IP to the client. // ApplyProxiedIP applies the given IP to the client.
func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls bool) (err error, quitMsg string) { func (client *Client) ApplyProxiedIP(session *Session, proxiedIP net.IP, tls bool) (err error, quitMsg string) {
// PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself // PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself
// is whitelisted: // is whitelisted:
if session.isTor { if session.isTor {
@ -66,12 +63,12 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
} }
// ensure IP is sane // ensure IP is sane
parsedProxiedIP := net.ParseIP(proxiedIP).To16() if proxiedIP == nil {
if parsedProxiedIP == nil { return errBadProxyLine, "proxied IP is not valid"
return errBadProxyLine, fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP)
} }
proxiedIP = proxiedIP.To16()
isBanned, banMsg := client.server.checkBans(parsedProxiedIP) isBanned, banMsg := client.server.checkBans(proxiedIP)
if isBanned { if isBanned {
return errBanned, banMsg return errBanned, banMsg
} }
@ -80,12 +77,12 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
client.server.connectionLimiter.RemoveClient(session.realIP) client.server.connectionLimiter.RemoveClient(session.realIP)
// given IP is sane! override the client's current IP // given IP is sane! override the client's current IP
client.server.logger.Info("connect-ip", "Accepted proxy IP for client", parsedProxiedIP.String()) client.server.logger.Info("connect-ip", "Accepted proxy IP for client", proxiedIP.String())
client.stateMutex.Lock() client.stateMutex.Lock()
defer client.stateMutex.Unlock() defer client.stateMutex.Unlock()
client.proxiedIP = parsedProxiedIP client.proxiedIP = proxiedIP
session.proxiedIP = parsedProxiedIP session.proxiedIP = proxiedIP
// nickmask will be updated when the client completes registration // nickmask will be updated when the client completes registration
// set tls info // set tls info
session.certfp = "" session.certfp = ""
@ -110,50 +107,17 @@ func handleProxyCommand(server *Server, client *Client, session *Session, line s
} }
}() }()
params := strings.Fields(line) ip, err := utils.ParseProxyLine(line)
if len(params) != 6 { if err != nil {
return errBadProxyLine return err
} }
if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) { if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
// assume PROXY connections are always secure // assume PROXY connections are always secure
err, quitMsg = client.ApplyProxiedIP(session, params[2], true) err, quitMsg = client.ApplyProxiedIP(session, ip, true)
return return
} else { } else {
// real source IP is not authorized to issue PROXY: // real source IP is not authorized to issue PROXY:
return errBadGatewayAddress return errBadGatewayAddress
} }
} }
// read a PROXY line one byte at a time, to ensure we don't read anything beyond
// that into a buffer, which would break the TLS handshake
func readRawProxyLine(conn net.Conn) (result string) {
// normally this is covered by ping timeouts, but we're doing this outside
// of the normal client goroutine:
conn.SetDeadline(time.Now().Add(time.Minute))
defer conn.SetDeadline(time.Time{})
var buf [maxProxyLineLen]byte
oneByte := make([]byte, 1)
i := 0
for i < maxProxyLineLen {
n, err := conn.Read(oneByte)
if err != nil {
return
} else if n == 1 {
buf[i] = oneByte[0]
if buf[i] == '\n' {
candidate := string(buf[0 : i+1])
if strings.HasPrefix(candidate, "PROXY") {
return candidate
} else {
return
}
}
i += 1
}
}
// no \r\n, fail out
return
}

View File

@ -10,6 +10,7 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net"
"os" "os"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
@ -2581,7 +2582,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
continue continue
} }
err, quitMsg := client.ApplyProxiedIP(rb.session, msg.Params[3], secure) err, quitMsg := client.ApplyProxiedIP(rb.session, net.ParseIP(msg.Params[3]), secure)
if err != nil { if err != nil {
client.Quit(quitMsg, rb.session) client.Quit(quitMsg, rb.session)
return true return true

135
irc/ircconn.go Normal file
View File

@ -0,0 +1,135 @@
package irc
import (
"bufio"
"bytes"
"net"
"unicode/utf8"
"github.com/gorilla/websocket"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/utils"
)
const (
maxReadQBytes = ircmsg.MaxlenTagsFromClient + 512 + 1024
)
var (
crlf = []byte{'\r', '\n'}
)
// IRCConn abstracts away the distinction between a regular
// net.Conn (which includes both raw TCP and TLS) and a websocket.
// it doesn't expose the net.Conn, io.Reader, or io.Writer interfaces
// because websockets are message-oriented, not stream-oriented, and
// therefore this abstraction is message-oriented as well.
type IRCConn interface {
UnderlyingConn() *utils.WrappedConn
// these take an IRC line or lines, correctly terminated with CRLF:
WriteLine([]byte) error
WriteLines([][]byte) error
// this returns an IRC line without the terminating CRLF:
ReadLine() (line []byte, err error)
Close() error
}
// IRCStreamConn is an IRCConn over a regular stream connection.
type IRCStreamConn struct {
conn *utils.WrappedConn
reader *bufio.Reader
}
func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
return &IRCStreamConn{
conn: conn,
}
}
func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
return cc.conn
}
func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
_, err = cc.conn.Write(buf)
return
}
func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
// on Linux, with a plaintext TCP or Unix domain socket,
// the Go runtime will optimize this into a single writev(2) call:
_, err = (*net.Buffers)(&buffers).WriteTo(cc.conn)
return
}
func (cc *IRCStreamConn) ReadLine() (line []byte, err error) {
// lazy initialize the reader in case the IP is banned
if cc.reader == nil {
cc.reader = bufio.NewReaderSize(cc.conn, maxReadQBytes)
}
var isPrefix bool
line, isPrefix, err = cc.reader.ReadLine()
if isPrefix {
return nil, errReadQ
}
line = bytes.TrimSuffix(line, crlf)
return
}
func (cc *IRCStreamConn) Close() (err error) {
return cc.conn.Close()
}
// IRCWSConn is an IRCConn over a websocket.
type IRCWSConn struct {
conn *websocket.Conn
}
func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
return IRCWSConn{conn: conn}
}
func (wc IRCWSConn) UnderlyingConn() *utils.WrappedConn {
// just assume that the type is OK
wConn, _ := wc.conn.UnderlyingConn().(*utils.WrappedConn)
return wConn
}
func (wc IRCWSConn) WriteLine(buf []byte) (err error) {
buf = bytes.TrimSuffix(buf, crlf)
// there's not much we can do about this;
// silently drop the message
if !utf8.Valid(buf) {
return nil
}
return wc.conn.WriteMessage(websocket.TextMessage, buf)
}
func (wc IRCWSConn) WriteLines(buffers [][]byte) (err error) {
for _, buf := range buffers {
err = wc.WriteLine(buf)
if err != nil {
return
}
}
return
}
func (wc IRCWSConn) ReadLine() (line []byte, err error) {
for {
var messageType int
messageType, line, err = wc.conn.ReadMessage()
// on empty message or non-text message, try again, block if necessary
if err != nil || (messageType == websocket.TextMessage && len(line) != 0) {
return
}
}
}
func (wc IRCWSConn) Close() (err error) {
return wc.conn.Close()
}

209
irc/listeners.go Normal file
View File

@ -0,0 +1,209 @@
// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"errors"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/oragono/oragono/irc/utils"
)
var (
errCantReloadListener = errors.New("can't switch a listener between stream and websocket")
)
// IRCListener is an abstract wrapper for a listener (TCP port or unix domain socket).
// Server tracks these by listen address and can reload or stop them during rehash.
type IRCListener interface {
Reload(config utils.ListenerConfig) error
Stop() error
}
// NewListener creates a new listener according to the specifications in the config file
func NewListener(server *Server, addr string, config utils.ListenerConfig, bindMode os.FileMode) (result IRCListener, err error) {
baseListener, err := createBaseListener(addr, bindMode)
if err != nil {
return
}
wrappedListener := utils.NewReloadableListener(baseListener, config)
if config.WebSocket {
return NewWSListener(server, addr, wrappedListener, config)
} else {
return NewNetListener(server, addr, wrappedListener, config)
}
}
func createBaseListener(addr string, bindMode os.FileMode) (listener net.Listener, err error) {
addr = strings.TrimPrefix(addr, "unix:")
if strings.HasPrefix(addr, "/") {
// https://stackoverflow.com/a/34881585
os.Remove(addr)
listener, err = net.Listen("unix", addr)
if err == nil && bindMode != 0 {
os.Chmod(addr, bindMode)
}
} else {
listener, err = net.Listen("tcp", addr)
}
return
}
// NetListener is an IRCListener for a regular stream socket (TCP or unix domain)
type NetListener struct {
listener *utils.ReloadableListener
server *Server
addr string
}
func NewNetListener(server *Server, addr string, listener *utils.ReloadableListener, config utils.ListenerConfig) (result *NetListener, err error) {
nl := NetListener{
server: server,
listener: listener,
addr: addr,
}
go nl.serve()
return &nl, nil
}
func (nl *NetListener) Reload(config utils.ListenerConfig) error {
if config.WebSocket {
return errCantReloadListener
}
nl.listener.Reload(config)
return nil
}
func (nl *NetListener) Stop() error {
return nl.listener.Close()
}
// ensure that any IP we got from the PROXY line is trustworthy (otherwise, clear it)
func validateProxiedIP(conn *utils.WrappedConn, config *Config) {
if !utils.IPInNets(utils.AddrToIP(conn.RemoteAddr()), config.Server.proxyAllowedFromNets) {
conn.ProxiedIP = nil
}
}
func (nl *NetListener) serve() {
for {
conn, err := nl.listener.Accept()
if err == nil {
// hand off the connection
wConn, ok := conn.(*utils.WrappedConn)
if ok {
if wConn.ProxiedIP != nil {
validateProxiedIP(wConn, nl.server.Config())
}
go nl.server.RunClient(NewIRCStreamConn(wConn))
} else {
nl.server.logger.Error("internal", "invalid connection type", nl.addr)
}
} else if err == utils.ErrNetClosing {
return
} else {
nl.server.logger.Error("internal", "accept error", nl.addr, err.Error())
}
}
}
// WSListener is a listener for IRC-over-websockets (initially HTTP, then upgraded to a
// different application protocol that provides a message-based API, possibly with TLS)
type WSListener struct {
sync.Mutex // tier 1
listener *utils.ReloadableListener
httpServer *http.Server
server *Server
addr string
config utils.ListenerConfig
}
func NewWSListener(server *Server, addr string, listener *utils.ReloadableListener, config utils.ListenerConfig) (result *WSListener, err error) {
result = &WSListener{
listener: listener,
server: server,
addr: addr,
config: config,
}
result.httpServer = &http.Server{
Handler: http.HandlerFunc(result.handle),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
go result.httpServer.Serve(listener)
return
}
func (wl *WSListener) Reload(config utils.ListenerConfig) error {
if !config.WebSocket {
return errCantReloadListener
}
wl.listener.Reload(config)
return nil
}
func (wl *WSListener) Stop() error {
return wl.httpServer.Close()
}
func (wl *WSListener) handle(w http.ResponseWriter, r *http.Request) {
config := wl.server.Config()
proxyAllowedFrom := config.Server.proxyAllowedFromNets
proxiedIP := utils.HandleXForwardedFor(r.RemoteAddr, r.Header.Get("X-Forwarded-For"), proxyAllowedFrom)
wsUpgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
if len(config.Server.WebSockets.allowedOriginRegexps) == 0 {
return true
}
origin := strings.TrimSpace(r.Header.Get("Origin"))
if len(origin) == 0 {
return false
}
for _, re := range config.Server.WebSockets.allowedOriginRegexps {
if re.MatchString(origin) {
return true
}
}
return false
},
}
conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil {
wl.server.logger.Info("internal", "websocket upgrade error", wl.addr, err.Error())
return
}
wConn, ok := conn.UnderlyingConn().(*utils.WrappedConn)
if !ok {
wl.server.logger.Error("internal", "non-proxied connection on websocket", wl.addr)
conn.Close()
return
}
if wConn.ProxiedIP != nil {
validateProxiedIP(wConn, config)
} else {
// if there was no PROXY protocol IP, use the validated X-Forwarded-For IP instead,
// unless it is redundant
if proxiedIP != nil && !proxiedIP.Equal(utils.AddrToIP(wConn.RemoteAddr())) {
wConn.ProxiedIP = proxiedIP
}
}
// avoid a DoS attack from buffering excessively large messages:
conn.SetReadLimit(maxReadQBytes)
go wl.server.RunClient(NewIRCWSConn(conn))
}

View File

@ -7,7 +7,6 @@ package irc
import ( import (
"bufio" "bufio"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -29,6 +28,7 @@ import (
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/mysql" "github.com/oragono/oragono/irc/mysql"
"github.com/oragono/oragono/irc/sno" "github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
@ -52,15 +52,6 @@ var (
throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect." throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect."
) )
// ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
type ListenerWrapper struct {
// protects atomic update of config and shouldStop:
sync.Mutex // tier 1
listener net.Listener
config listenerConfig
shouldStop bool
}
// Server is the main Oragono server. // Server is the main Oragono server.
type Server struct { type Server struct {
accounts AccountManager accounts AccountManager
@ -74,7 +65,7 @@ type Server struct {
dlines *DLineManager dlines *DLineManager
helpIndexManager HelpIndexManager helpIndexManager HelpIndexManager
klines *KLineManager klines *KLineManager
listeners map[string]*ListenerWrapper listeners map[string]IRCListener
logger *logger.Manager logger *logger.Manager
monitorManager MonitorManager monitorManager MonitorManager
name string name string
@ -102,17 +93,12 @@ var (
} }
) )
type clientConn struct {
Conn net.Conn
Config listenerConfig
}
// NewServer returns a new Oragono server. // NewServer returns a new Oragono server.
func NewServer(config *Config, logger *logger.Manager) (*Server, error) { func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
// initialize data structures // initialize data structures
server := &Server{ server := &Server{
ctime: time.Now().UTC(), ctime: time.Now().UTC(),
listeners: make(map[string]*ListenerWrapper), listeners: make(map[string]IRCListener),
logger: logger, logger: logger,
rehashSignal: make(chan os.Signal, 1), rehashSignal: make(chan os.Signal, 1),
signals: make(chan os.Signal, len(ServerExitSignals)), signals: make(chan os.Signal, len(ServerExitSignals)),
@ -220,84 +206,6 @@ func (server *Server) checkTorLimits() (banned bool, message string) {
} }
} }
//
// IRC protocol listeners
//
// createListener starts a given listener.
func (server *Server) createListener(addr string, conf listenerConfig, bindMode os.FileMode) (*ListenerWrapper, error) {
// make listener
var listener net.Listener
var err error
addr = strings.TrimPrefix(addr, "unix:")
if strings.HasPrefix(addr, "/") {
// https://stackoverflow.com/a/34881585
os.Remove(addr)
listener, err = net.Listen("unix", addr)
if err == nil && bindMode != 0 {
os.Chmod(addr, bindMode)
}
} else {
listener, err = net.Listen("tcp", addr)
}
if err != nil {
return nil, err
}
// throw our details to the server so we can be modified/killed later
wrapper := ListenerWrapper{
listener: listener,
config: conf,
shouldStop: false,
}
var shouldStop bool
// setup accept goroutine
go func() {
for {
conn, err := listener.Accept()
// synchronously access config data:
wrapper.Lock()
shouldStop = wrapper.shouldStop
conf := wrapper.config
wrapper.Unlock()
if shouldStop {
if conn != nil {
conn.Close()
}
listener.Close()
return
} else if err == nil {
var proxyLine string
if conf.ProxyBeforeTLS {
proxyLine = readRawProxyLine(conn)
if proxyLine == "" {
server.logger.Error("internal", "bad TLS-proxy line from", addr)
conn.Close()
continue
}
}
if conf.TLSConfig != nil {
conn = tls.Server(conn, conf.TLSConfig)
}
newConn := clientConn{
Conn: conn,
Config: conf,
}
// hand off the connection
go server.RunClient(newConn, proxyLine)
} else {
server.logger.Error("internal", "accept error", addr, err.Error())
}
}
}()
return &wrapper, nil
}
// //
// server functionality // server functionality
// //
@ -816,9 +724,9 @@ func (server *Server) loadDatastore(config *Config) error {
} }
func (server *Server) setupListeners(config *Config) (err error) { func (server *Server) setupListeners(config *Config) (err error) {
logListener := func(addr string, config listenerConfig) { logListener := func(addr string, config utils.ListenerConfig) {
server.logger.Info("listeners", server.logger.Info("listeners",
fmt.Sprintf("now listening on %s, tls=%t, tlsproxy=%t, tor=%t.", addr, (config.TLSConfig != nil), config.ProxyBeforeTLS, config.Tor), fmt.Sprintf("now listening on %s, tls=%t, tlsproxy=%t, tor=%t, websocket=%t.", addr, (config.TLSConfig != nil), config.RequireProxy, config.Tor, config.WebSocket),
) )
} }
@ -827,16 +735,22 @@ func (server *Server) setupListeners(config *Config) (err error) {
currentListener := server.listeners[addr] currentListener := server.listeners[addr]
newConfig, stillConfigured := config.Server.trueListeners[addr] newConfig, stillConfigured := config.Server.trueListeners[addr]
currentListener.Lock()
currentListener.shouldStop = !stillConfigured
currentListener.config = newConfig
currentListener.Unlock()
if stillConfigured { if stillConfigured {
reloadErr := currentListener.Reload(newConfig)
// attempt to stop and replace the listener if the reload failed
if reloadErr != nil {
currentListener.Stop()
newListener, newErr := NewListener(server, addr, newConfig, config.Server.UnixBindMode)
if newErr == nil {
server.listeners[addr] = newListener
} else {
delete(server.listeners, addr)
return newErr
}
}
logListener(addr, newConfig) logListener(addr, newConfig)
} else { } else {
// tell the listener it should stop by interrupting its Accept() call: currentListener.Stop()
currentListener.listener.Close()
delete(server.listeners, addr) delete(server.listeners, addr)
server.logger.Info("listeners", fmt.Sprintf("stopped listening on %s.", addr)) server.logger.Info("listeners", fmt.Sprintf("stopped listening on %s.", addr))
} }
@ -850,15 +764,15 @@ func (server *Server) setupListeners(config *Config) (err error) {
} }
_, exists := server.listeners[newAddr] _, exists := server.listeners[newAddr]
if !exists { if !exists {
// make new listener // make a new listener
listener, listenerErr := server.createListener(newAddr, newConfig, config.Server.UnixBindMode) newListener, newErr := NewListener(server, newAddr, newConfig, config.Server.UnixBindMode)
if listenerErr != nil { if newErr == nil {
server.logger.Error("server", "couldn't listen on", newAddr, listenerErr.Error()) server.listeners[newAddr] = newListener
err = listenerErr logListener(newAddr, newConfig)
continue } else {
server.logger.Error("server", "couldn't listen on", newAddr, newErr.Error())
err = newErr
} }
server.listeners[newAddr] = listener
logListener(newAddr, newConfig)
} }
} }

View File

@ -5,22 +5,15 @@
package irc package irc
import ( import (
"bufio"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"errors" "errors"
"io" "io"
"net"
"strings" "strings"
"sync" "sync"
"time"
"github.com/oragono/oragono/irc/utils" "github.com/oragono/oragono/irc/utils"
) )
var ( var (
handshakeTimeout = RegisterTimeout
errSendQExceeded = errors.New("SendQ exceeded") errSendQExceeded = errors.New("SendQ exceeded")
sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n") sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
@ -30,8 +23,7 @@ var (
type Socket struct { type Socket struct {
sync.Mutex sync.Mutex
conn net.Conn conn IRCConn
reader *bufio.Reader
maxSendQBytes int maxSendQBytes int
@ -47,10 +39,9 @@ type Socket struct {
} }
// NewSocket returns a new Socket. // NewSocket returns a new Socket.
func NewSocket(conn net.Conn, maxReadQBytes int, maxSendQBytes int) *Socket { func NewSocket(conn IRCConn, maxSendQBytes int) *Socket {
result := Socket{ result := Socket{
conn: conn, conn: conn,
reader: bufio.NewReaderSize(conn, maxReadQBytes),
maxSendQBytes: maxSendQBytes, maxSendQBytes: maxSendQBytes,
} }
result.writerSemaphore.Initialize(1) result.writerSemaphore.Initialize(1)
@ -66,43 +57,13 @@ func (socket *Socket) Close() {
socket.wakeWriter() socket.wakeWriter()
} }
// CertFP returns the fingerprint of the certificate provided by the client.
func (socket *Socket) CertFP() (string, error) {
var tlsConn, isTLS = socket.conn.(*tls.Conn)
if !isTLS {
return "", errNotTLS
}
// ensure handehake is performed, and timeout after a few seconds
tlsConn.SetDeadline(time.Now().Add(handshakeTimeout))
err := tlsConn.Handshake()
tlsConn.SetDeadline(time.Time{})
if err != nil {
return "", err
}
peerCerts := tlsConn.ConnectionState().PeerCertificates
if len(peerCerts) < 1 {
return "", errNoPeerCerts
}
rawCert := sha256.Sum256(peerCerts[0].Raw)
fingerprint := hex.EncodeToString(rawCert[:])
return fingerprint, nil
}
// Read returns a single IRC line from a Socket. // Read returns a single IRC line from a Socket.
func (socket *Socket) Read() (string, error) { func (socket *Socket) Read() (string, error) {
if socket.IsClosed() { if socket.IsClosed() {
return "", io.EOF return "", io.EOF
} }
lineBytes, isPrefix, err := socket.reader.ReadLine() lineBytes, err := socket.conn.ReadLine()
if isPrefix {
return "", errReadQ
}
// convert bytes to string // convert bytes to string
line := string(lineBytes) line := string(lineBytes)
@ -183,7 +144,7 @@ func (socket *Socket) BlockingWrite(data []byte) (err error) {
return io.EOF return io.EOF
} }
_, err = socket.conn.Write(data) err = socket.conn.WriteLine(data)
if err != nil { if err != nil {
socket.finalize() socket.finalize()
} }
@ -255,8 +216,7 @@ func (socket *Socket) performWrite() (closed bool) {
var err error var err error
if 0 < len(buffers) { if 0 < len(buffers) {
// on Linux, the runtime will optimize this into a single writev(2) call: err = socket.conn.WriteLines(buffers)
_, err = (*net.Buffers)(&buffers).WriteTo(socket.conn)
} }
closed = closed || err != nil closed = closed || err != nil
@ -284,7 +244,7 @@ func (socket *Socket) finalize() {
} }
if len(finalData) != 0 { if len(finalData) != 0 {
socket.conn.Write(finalData) socket.conn.WriteLine(finalData)
} }
// close the connection // close the connection

View File

@ -5,12 +5,16 @@ package utils
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha256"
"crypto/subtle" "crypto/subtle"
"crypto/tls"
"encoding/base32" "encoding/base32"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
"net"
"strings" "strings"
"time"
) )
var ( var (
@ -18,6 +22,10 @@ var (
B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding) B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
ErrInvalidCertfp = errors.New("Invalid certfp") ErrInvalidCertfp = errors.New("Invalid certfp")
ErrNoPeerCerts = errors.New("No certfp available")
ErrNotTLS = errors.New("Connection is not TLS")
) )
const ( const (
@ -83,3 +91,29 @@ func NormalizeCertfp(certfp string) (result string, err error) {
} }
return return
} }
func GetCertFP(conn net.Conn, handshakeTimeout time.Duration) (result string, err error) {
tlsConn, isTLS := conn.(*tls.Conn)
if !isTLS {
return "", ErrNotTLS
}
// ensure handshake is performed
tlsConn.SetDeadline(time.Now().Add(handshakeTimeout))
err = tlsConn.Handshake()
tlsConn.SetDeadline(time.Time{})
if err != nil {
return "", err
}
peerCerts := tlsConn.ConnectionState().PeerCertificates
if len(peerCerts) < 1 {
return "", ErrNoPeerCerts
}
rawCert := sha256.Sum256(peerCerts[0].Raw)
fingerprint := hex.EncodeToString(rawCert[:])
return fingerprint, nil
}

31
irc/utils/glob.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package utils
import (
"bytes"
"regexp"
"regexp/syntax"
)
// yet another glob implementation in Go
func CompileGlob(glob string) (result *regexp.Regexp, err error) {
var buf bytes.Buffer
buf.WriteByte('^')
for _, r := range glob {
switch r {
case '*':
buf.WriteString("(.*)")
case '?':
buf.WriteString("(.)")
case 0xFFFD:
return nil, &syntax.Error{Code: syntax.ErrInvalidUTF8, Expr: glob}
default:
buf.WriteString(regexp.QuoteMeta(string(r)))
}
}
buf.WriteByte('$')
return regexp.Compile(buf.String())
}

48
irc/utils/glob_test.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package utils
import (
"regexp"
"testing"
)
func globMustCompile(glob string) *regexp.Regexp {
re, err := CompileGlob(glob)
if err != nil {
panic(err)
}
return re
}
func assertMatches(glob, str string, match bool, t *testing.T) {
re := globMustCompile(glob)
if re.MatchString(str) != match {
t.Errorf("should %s match %s? %t, but got %t instead", glob, str, match, !match)
}
}
func TestGlob(t *testing.T) {
assertMatches("https://testnet.oragono.io", "https://testnet.oragono.io", true, t)
assertMatches("https://*.oragono.io", "https://testnet.oragono.io", true, t)
assertMatches("*://*.oragono.io", "https://testnet.oragono.io", true, t)
assertMatches("*://*.oragono.io", "https://oragono.io", false, t)
assertMatches("*://*.oragono.io", "https://githubusercontent.com", false, t)
assertMatches("*://*.oragono.io", "https://testnet.oragono.io.example.com", false, t)
assertMatches("", "", true, t)
assertMatches("", "x", false, t)
assertMatches("*", "", true, t)
assertMatches("*", "x", true, t)
assertMatches("c?b", "cab", true, t)
assertMatches("c?b", "cub", true, t)
assertMatches("c?b", "cb", false, t)
assertMatches("c?b", "cube", false, t)
assertMatches("?*", "cube", true, t)
assertMatches("?*", "", false, t)
assertMatches("S*e", "Skåne", true, t)
assertMatches("Sk?ne", "Skåne", true, t)
}

View File

@ -22,19 +22,13 @@ var (
func AddrToIP(addr net.Addr) net.IP { func AddrToIP(addr net.Addr) net.IP {
if tcpaddr, ok := addr.(*net.TCPAddr); ok { if tcpaddr, ok := addr.(*net.TCPAddr); ok {
return tcpaddr.IP.To16() return tcpaddr.IP.To16()
} else if AddrIsUnix(addr) { } else if _, ok := addr.(*net.UnixAddr); ok {
return IPv4LoopbackAddress return IPv4LoopbackAddress
} else { } else {
return nil return nil
} }
} }
// AddrIsUnix returns whether the address is a unix domain socket.
func AddrIsUnix(addr net.Addr) bool {
_, ok := addr.(*net.UnixAddr)
return ok
}
// IPStringToHostname converts a string representation of an IP address to an IRC-ready hostname // IPStringToHostname converts a string representation of an IP address to an IRC-ready hostname
func IPStringToHostname(ipStr string) string { func IPStringToHostname(ipStr string) string {
if 0 < len(ipStr) && ipStr[0] == ':' { if 0 < len(ipStr) && ipStr[0] == ':' {
@ -158,3 +152,44 @@ func ParseNetList(netList []string) (nets []net.IPNet, err error) {
} }
return return
} }
// Process the X-Forwarded-For header, validating against a list of trusted IPs.
// Returns the address that the request was forwarded for, or nil if no trustworthy
// data was available.
func HandleXForwardedFor(remoteAddr string, xForwardedFor string, whitelist []net.IPNet) (result net.IP) {
// http.Request.RemoteAddr "has no defined format". with TCP it's typically "127.0.0.1:23784",
// with unix domain it's typically "@"
var remoteIP net.IP
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
remoteIP = IPv4LoopbackAddress
} else {
remoteIP = net.ParseIP(host)
}
if remoteIP == nil || !IPInNets(remoteIP, whitelist) {
return remoteIP
}
// walk backwards through the X-Forwarded-For chain looking for an IP
// that is *not* trusted. that means it was added by one of our trusted
// forwarders (either remoteIP or something ahead of it in the chain)
// and we can trust it:
result = remoteIP
forwardedIPs := strings.Split(xForwardedFor, ",")
for i := len(forwardedIPs) - 1; i >= 0; i-- {
proxiedIP := net.ParseIP(strings.TrimSpace(forwardedIPs[i]))
if proxiedIP == nil {
return
} else if !IPInNets(proxiedIP, whitelist) {
return proxiedIP
} else {
result = proxiedIP
}
}
// no valid untrusted IPs were found in the chain;
// return either the last valid and trusted IP (which must be the origin),
// or nil:
return
}

View File

@ -159,3 +159,49 @@ func TestNormalizedNetFromString(t *testing.T) {
assertEqual(NetToNormalizedString(network), "2001:db8::1", t) assertEqual(NetToNormalizedString(network), "2001:db8::1", t)
assertEqual(network.Contains(net.ParseIP("2001:0db8::1")), true, t) assertEqual(network.Contains(net.ParseIP("2001:0db8::1")), true, t)
} }
func checkXFF(remoteAddr, forwardedHeader string, expectedStr string, t *testing.T) {
whitelistCIDRs := []string{"10.0.0.0/8", "127.0.0.1/8"}
var whitelist []net.IPNet
for _, str := range whitelistCIDRs {
_, wlNet, err := net.ParseCIDR(str)
if err != nil {
panic(err)
}
whitelist = append(whitelist, *wlNet)
}
expected := net.ParseIP(expectedStr)
actual := HandleXForwardedFor(remoteAddr, forwardedHeader, whitelist)
if !actual.Equal(expected) {
t.Errorf("handling %s and %s, expected %s, got %s", remoteAddr, forwardedHeader, expected, actual)
}
}
func TestXForwardedFor(t *testing.T) {
checkXFF("8.8.4.4:9999", "", "8.8.4.4", t)
// forged XFF header from untrustworthy external IP, should be ignored:
checkXFF("8.8.4.4:9999", "1.1.1.1", "8.8.4.4", t)
checkXFF("10.0.0.4:28432", "", "10.0.0.4", t)
checkXFF("10.0.0.4:28432", "8.8.4.4", "8.8.4.4", t)
checkXFF("10.0.0.4:28432", "10.0.0.3", "10.0.0.3", t)
checkXFF("10.0.0.4:28432", "1.1.1.1, 8.8.4.4", "8.8.4.4", t)
checkXFF("10.0.0.4:28432", "1.1.1.1,8.8.4.4", "8.8.4.4", t)
checkXFF("10.0.0.4:28432", "8.8.4.4, 1.1.1.1, 10.0.0.3", "1.1.1.1", t)
checkXFF("10.0.0.4:28432", "10.0.0.1, 10.0.0.2, 10.0.0.3", "10.0.0.1", t)
checkXFF("10.0.0.4:28432", "10.0.0.1, 10.0.0.2,10.0.0.3", "10.0.0.1", t)
checkXFF("@", "8.8.4.4, 1.1.1.1, 10.0.0.3", "1.1.1.1", t)
// invalid IP tests:
checkXFF("8.8.4.4:9999", "not_an_ip", "8.8.4.4", t)
checkXFF("10.0.0.4:28432", "not_an_ip", "10.0.0.4", t)
checkXFF("10.0.0.4:28432", "not_an_ip, 1.1.1.1, 10.0.0.3", "1.1.1.1", t)
checkXFF("10.0.0.4:28432", "8.8.4.4, not_an_ip, 10.0.0.3", "10.0.0.3", t)
checkXFF("10.0.0.4:28432", "8.8.4.4, not_an_ip", "10.0.0.4", t)
}

174
irc/utils/proxy.go Normal file
View File

@ -0,0 +1,174 @@
// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package utils
import (
"crypto/tls"
"errors"
"net"
"strings"
"sync"
"time"
)
// TODO: handle PROXY protocol v2 (the binary protocol)
const (
// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
// "a 108-byte buffer is always enough to store all the line and a trailing zero
// for string processing."
maxProxyLineLen = 107
)
var (
ErrBadProxyLine = errors.New("invalid PROXY line")
// TODO(golang/go#4373): replace this with the stdlib ErrNetClosing
ErrNetClosing = errors.New("use of closed network connection")
)
// ListenerConfig is all the information about how to process
// incoming IRC connections on a listener.
type ListenerConfig struct {
TLSConfig *tls.Config
ProxyDeadline time.Duration
RequireProxy bool
// these are just metadata for easier tracking,
// they are not used by ReloadableListener:
Tor bool
STSOnly bool
WebSocket bool
}
// read a PROXY line one byte at a time, to ensure we don't read anything beyond
// that into a buffer, which would break the TLS handshake
func readRawProxyLine(conn net.Conn, deadline time.Duration) (result string) {
// normally this is covered by ping timeouts, but we're doing this outside
// of the normal client goroutine:
conn.SetDeadline(time.Now().Add(deadline))
defer conn.SetDeadline(time.Time{})
var buf [maxProxyLineLen]byte
oneByte := make([]byte, 1)
i := 0
for i < maxProxyLineLen {
n, err := conn.Read(oneByte)
if err != nil {
return
} else if n == 1 {
buf[i] = oneByte[0]
if buf[i] == '\n' {
candidate := string(buf[0 : i+1])
if strings.HasPrefix(candidate, "PROXY") {
return candidate
} else {
return
}
}
i += 1
}
}
// no \r\n, fail out
return
}
// ParseProxyLine parses a PROXY protocol (v1) line and returns the remote IP.
func ParseProxyLine(line string) (ip net.IP, err error) {
params := strings.Fields(line)
if len(params) != 6 || params[0] != "PROXY" {
return nil, ErrBadProxyLine
}
ip = net.ParseIP(params[2])
if ip == nil {
return nil, ErrBadProxyLine
}
return ip.To16(), nil
}
/// WrappedConn is a net.Conn with some additional data stapled to it;
// the proxied IP, if one was read via the PROXY protocol, and the listener
// configuration.
type WrappedConn struct {
net.Conn
ProxiedIP net.IP
Config ListenerConfig
}
// ReloadableListener is a wrapper for net.Listener that allows reloading
// of config data for postprocessing connections (TLS, PROXY protocol, etc.)
type ReloadableListener struct {
// TODO: make this lock-free
sync.Mutex
realListener net.Listener
config ListenerConfig
isClosed bool
}
func NewReloadableListener(realListener net.Listener, config ListenerConfig) *ReloadableListener {
return &ReloadableListener{
realListener: realListener,
config: config,
}
}
func (rl *ReloadableListener) Reload(config ListenerConfig) {
rl.Lock()
rl.config = config
rl.Unlock()
}
func (rl *ReloadableListener) Accept() (conn net.Conn, err error) {
conn, err = rl.realListener.Accept()
rl.Lock()
config := rl.config
isClosed := rl.isClosed
rl.Unlock()
if isClosed {
if err == nil {
conn.Close()
}
err = ErrNetClosing
}
if err != nil {
return nil, err
}
var proxiedIP net.IP
if config.RequireProxy {
// this will occur synchronously on the goroutine calling Accept(),
// but that's OK because this listener *requires* a PROXY line,
// therefore it must be used with proxies that always send the line
// and we won't get slowloris'ed waiting for the client response
proxyLine := readRawProxyLine(conn, config.ProxyDeadline)
proxiedIP, err = ParseProxyLine(proxyLine)
if err != nil {
conn.Close()
return nil, err
}
}
if config.TLSConfig != nil {
conn = tls.Server(conn, config.TLSConfig)
}
return &WrappedConn{
Conn: conn,
ProxiedIP: proxiedIP,
Config: config,
}, nil
}
func (rl *ReloadableListener) Close() error {
rl.Lock()
rl.isClosed = true
rl.Unlock()
return rl.realListener.Close()
}
func (rl *ReloadableListener) Addr() net.Addr {
return rl.realListener.Addr()
}

View File

@ -61,6 +61,13 @@ server:
# "/hidden_service_sockets/oragono_tor_sock": # "/hidden_service_sockets/oragono_tor_sock":
# tor: true # tor: true
# Example of a WebSocket listener:
# ":4430":
# websocket: true
# tls:
# key: tls.key
# cert: tls.crt
# sets the permissions for Unix listen sockets. on a typical Linux system, # sets the permissions for Unix listen sockets. on a typical Linux system,
# the default is 0775 or 0755, which prevents other users/groups from connecting # the default is 0775 or 0755, which prevents other users/groups from connecting
# to the socket. With 0777, it behaves like a normal TCP socket # to the socket. With 0777, it behaves like a normal TCP socket
@ -102,6 +109,16 @@ server:
# should clients include this STS policy when they ship their inbuilt preload lists? # should clients include this STS policy when they ship their inbuilt preload lists?
preload: false preload: false
websockets:
# Restrict the origin of WebSocket connections by matching the "Origin" HTTP
# header. This settings makes oragono reject every WebSocket connection,
# except when it originates from one of the hosts in this list. Use this to
# prevent malicious websites from making their visitors connect to oragono
# without their knowledge. An empty list means that there are no restrictions.
allowed-origins:
# - "https://oragono.io"
# - "https://*.oragono.io"
# casemapping controls what kinds of strings are permitted as identifiers (nicknames, # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
# channel names, account names, etc.), and how they are normalized for case. # channel names, account names, etc.), and how they are normalized for case.
# with the recommended default of 'precis', utf-8 identifiers that are "sane" # with the recommended default of 'precis', utf-8 identifiers that are "sane"

25
vendor/github.com/gorilla/websocket/.gitignore generated vendored Normal file
View File

@ -0,0 +1,25 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.idea/
*.iml

9
vendor/github.com/gorilla/websocket/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,9 @@
# This is the official list of Gorilla WebSocket authors for copyright
# purposes.
#
# Please keep the list sorted.
Gary Burd <gary@beagledreams.com>
Google LLC (https://opensource.google.com/)
Joachim Bauch <mail@joachim-bauch.de>

22
vendor/github.com/gorilla/websocket/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

64
vendor/github.com/gorilla/websocket/README.md generated vendored Normal file
View File

@ -0,0 +1,64 @@
# Gorilla WebSocket
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket)
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
### Documentation
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
### Status
The Gorilla WebSocket package provides a complete and tested implementation of
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
package API is stable.
### Installation
go get github.com/gorilla/websocket
### Protocol Compliance
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
<table>
<tr>
<th></th>
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
</tr>
<tr>
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
<tr><td colspan="3">Other Features</tr></td>
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
</table>
Notes:
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
2. The application can get the type of a received data message by implementing
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
function.
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
Read returns when the input buffer is full or a frame boundary is
encountered. Each call to Write sends a single frame message. The Gorilla
io.Reader and io.WriteCloser operate on a single WebSocket message.

395
vendor/github.com/gorilla/websocket/client.go generated vendored Normal file
View File

@ -0,0 +1,395 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bytes"
"context"
"crypto/tls"
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"strings"
"time"
)
// ErrBadHandshake is returned when the server response to opening handshake is
// invalid.
var ErrBadHandshake = errors.New("websocket: bad handshake")
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
// NewClient creates a new client connection using the given net connection.
// The URL u specifies the host and request URI. Use requestHeader to specify
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
// (Cookie). Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etc.
//
// Deprecated: Use Dialer instead.
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
d := Dialer{
ReadBufferSize: readBufSize,
WriteBufferSize: writeBufSize,
NetDial: func(net, addr string) (net.Conn, error) {
return netConn, nil
},
}
return d.Dial(u.String(), requestHeader)
}
// A Dialer contains options for connecting to WebSocket server.
type Dialer struct {
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
// NetDialContext specifies the dial function for creating TCP connections. If
// NetDialContext is nil, net.DialContext is used.
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
// If Proxy is nil or returns a nil *URL, no proxy is used.
Proxy func(*http.Request) (*url.URL, error)
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
TLSClientConfig *tls.Config
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
// size is zero, then a useful default size is used. The I/O buffer sizes
// do not limit the size of the messages that can be sent or received.
ReadBufferSize, WriteBufferSize int
// WriteBufferPool is a pool of buffers for write operations. If the value
// is not set, then write buffers are allocated to the connection for the
// lifetime of the connection.
//
// A pool is most useful when the application has a modest volume of writes
// across a large number of connections.
//
// Applications should use a single pool for each unique value of
// WriteBufferSize.
WriteBufferPool BufferPool
// Subprotocols specifies the client's requested subprotocols.
Subprotocols []string
// EnableCompression specifies if the client should attempt to negotiate
// per message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
// Jar specifies the cookie jar.
// If Jar is nil, cookies are not sent in requests and ignored
// in responses.
Jar http.CookieJar
}
// Dial creates a new client connection by calling DialContext with a background context.
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
return d.DialContext(context.Background(), urlStr, requestHeader)
}
var errMalformedURL = errors.New("malformed ws or wss URL")
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
hostPort = u.Host
hostNoPort = u.Host
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
hostNoPort = hostNoPort[:i]
} else {
switch u.Scheme {
case "wss":
hostPort += ":443"
case "https":
hostPort += ":443"
default:
hostPort += ":80"
}
}
return hostPort, hostNoPort
}
// DefaultDialer is a dialer with all fields set to the default values.
var DefaultDialer = &Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
}
// nilDialer is dialer to use when receiver is nil.
var nilDialer = *DefaultDialer
// DialContext creates a new client connection. Use requestHeader to specify the
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// The context will be used in the request and in the Dialer.
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etcetera. The response body may not contain the entire response and does not
// need to be closed by the application.
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
if d == nil {
d = &nilDialer
}
challengeKey, err := generateChallengeKey()
if err != nil {
return nil, nil, err
}
u, err := url.Parse(urlStr)
if err != nil {
return nil, nil, err
}
switch u.Scheme {
case "ws":
u.Scheme = "http"
case "wss":
u.Scheme = "https"
default:
return nil, nil, errMalformedURL
}
if u.User != nil {
// User name and password are not allowed in websocket URIs.
return nil, nil, errMalformedURL
}
req := &http.Request{
Method: "GET",
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
req = req.WithContext(ctx)
// Set the cookies present in the cookie jar of the dialer
if d.Jar != nil {
for _, cookie := range d.Jar.Cookies(u) {
req.AddCookie(cookie)
}
}
// Set the request headers using the capitalization for names and values in
// RFC examples. Although the capitalization shouldn't matter, there are
// servers that depend on it. The Header.Set method is not used because the
// method canonicalizes the header names.
req.Header["Upgrade"] = []string{"websocket"}
req.Header["Connection"] = []string{"Upgrade"}
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
req.Header["Sec-WebSocket-Version"] = []string{"13"}
if len(d.Subprotocols) > 0 {
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
}
for k, vs := range requestHeader {
switch {
case k == "Host":
if len(vs) > 0 {
req.Host = vs[0]
}
case k == "Upgrade" ||
k == "Connection" ||
k == "Sec-Websocket-Key" ||
k == "Sec-Websocket-Version" ||
k == "Sec-Websocket-Extensions" ||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
case k == "Sec-Websocket-Protocol":
req.Header["Sec-WebSocket-Protocol"] = vs
default:
req.Header[k] = vs
}
}
if d.EnableCompression {
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
}
if d.HandshakeTimeout != 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
defer cancel()
}
// Get network dial function.
var netDial func(network, add string) (net.Conn, error)
if d.NetDialContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialContext(ctx, network, addr)
}
} else if d.NetDial != nil {
netDial = d.NetDial
} else {
netDialer := &net.Dialer{}
netDial = func(network, addr string) (net.Conn, error) {
return netDialer.DialContext(ctx, network, addr)
}
}
// If needed, wrap the dial function to set the connection deadline.
if deadline, ok := ctx.Deadline(); ok {
forwardDial := netDial
netDial = func(network, addr string) (net.Conn, error) {
c, err := forwardDial(network, addr)
if err != nil {
return nil, err
}
err = c.SetDeadline(deadline)
if err != nil {
c.Close()
return nil, err
}
return c, nil
}
}
// If needed, wrap the dial function to connect through a proxy.
if d.Proxy != nil {
proxyURL, err := d.Proxy(req)
if err != nil {
return nil, nil, err
}
if proxyURL != nil {
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
if err != nil {
return nil, nil, err
}
netDial = dialer.Dial
}
}
hostPort, hostNoPort := hostPortNoPort(u)
trace := httptrace.ContextClientTrace(ctx)
if trace != nil && trace.GetConn != nil {
trace.GetConn(hostPort)
}
netConn, err := netDial("tcp", hostPort)
if trace != nil && trace.GotConn != nil {
trace.GotConn(httptrace.GotConnInfo{
Conn: netConn,
})
}
if err != nil {
return nil, nil, err
}
defer func() {
if netConn != nil {
netConn.Close()
}
}()
if u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(netConn, cfg)
netConn = tlsConn
var err error
if trace != nil {
err = doHandshakeWithTrace(trace, tlsConn, cfg)
} else {
err = doHandshake(tlsConn, cfg)
}
if err != nil {
return nil, nil, err
}
}
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
if err := req.Write(netConn); err != nil {
return nil, nil, err
}
if trace != nil && trace.GotFirstResponseByte != nil {
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
trace.GotFirstResponseByte()
}
}
resp, err := http.ReadResponse(conn.br, req)
if err != nil {
return nil, nil, err
}
if d.Jar != nil {
if rc := resp.Cookies(); len(rc) > 0 {
d.Jar.SetCookies(u, rc)
}
}
if resp.StatusCode != 101 ||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
// Before closing the network connection on return from this
// function, slurp up some of the response to aid application
// debugging.
buf := make([]byte, 1024)
n, _ := io.ReadFull(resp.Body, buf)
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
return nil, resp, ErrBadHandshake
}
for _, ext := range parseExtensions(resp.Header) {
if ext[""] != "permessage-deflate" {
continue
}
_, snct := ext["server_no_context_takeover"]
_, cnct := ext["client_no_context_takeover"]
if !snct || !cnct {
return nil, resp, errInvalidCompression
}
conn.newCompressionWriter = compressNoContextTakeover
conn.newDecompressionReader = decompressNoContextTakeover
break
}
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
netConn.SetDeadline(time.Time{})
netConn = nil // to avoid close in defer.
return conn, resp, nil
}
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.Handshake(); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}

16
vendor/github.com/gorilla/websocket/client_clone.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View File

@ -0,0 +1,38 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
import "crypto/tls"
// cloneTLSConfig clones all public fields except the fields
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
// config in active use.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

148
vendor/github.com/gorilla/websocket/compression.go generated vendored Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"compress/flate"
"errors"
"io"
"strings"
"sync"
)
const (
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
maxCompressionLevel = flate.BestCompression
defaultCompressionLevel = 1
)
var (
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
flateReaderPool = sync.Pool{New: func() interface{} {
return flate.NewReader(nil)
}}
)
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
const tail =
// Add four bytes as specified in RFC
"\x00\x00\xff\xff" +
// Add final block to squelch unexpected EOF error from flate reader.
"\x01\x00\x00\xff\xff"
fr, _ := flateReaderPool.Get().(io.ReadCloser)
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
return &flateReadWrapper{fr}
}
func isValidCompressionLevel(level int) bool {
return minCompressionLevel <= level && level <= maxCompressionLevel
}
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
p := &flateWriterPools[level-minCompressionLevel]
tw := &truncWriter{w: w}
fw, _ := p.Get().(*flate.Writer)
if fw == nil {
fw, _ = flate.NewWriter(tw, level)
} else {
fw.Reset(tw)
}
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
}
// truncWriter is an io.Writer that writes all but the last four bytes of the
// stream to another io.Writer.
type truncWriter struct {
w io.WriteCloser
n int
p [4]byte
}
func (w *truncWriter) Write(p []byte) (int, error) {
n := 0
// fill buffer first for simplicity.
if w.n < len(w.p) {
n = copy(w.p[w.n:], p)
p = p[n:]
w.n += n
if len(p) == 0 {
return n, nil
}
}
m := len(p)
if m > len(w.p) {
m = len(w.p)
}
if nn, err := w.w.Write(w.p[:m]); err != nil {
return n + nn, err
}
copy(w.p[:], w.p[m:])
copy(w.p[len(w.p)-m:], p[len(p)-m:])
nn, err := w.w.Write(p[:len(p)-m])
return n + nn, err
}
type flateWriteWrapper struct {
fw *flate.Writer
tw *truncWriter
p *sync.Pool
}
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
if w.fw == nil {
return 0, errWriteClosed
}
return w.fw.Write(p)
}
func (w *flateWriteWrapper) Close() error {
if w.fw == nil {
return errWriteClosed
}
err1 := w.fw.Flush()
w.p.Put(w.fw)
w.fw = nil
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
}
err2 := w.tw.w.Close()
if err1 != nil {
return err1
}
return err2
}
type flateReadWrapper struct {
fr io.ReadCloser
}
func (r *flateReadWrapper) Read(p []byte) (int, error) {
if r.fr == nil {
return 0, io.ErrClosedPipe
}
n, err := r.fr.Read(p)
if err == io.EOF {
// Preemptively place the reader back in the pool. This helps with
// scenarios where the application does not call NextReader() soon after
// this final read.
r.Close()
}
return n, err
}
func (r *flateReadWrapper) Close() error {
if r.fr == nil {
return io.ErrClosedPipe
}
err := r.fr.Close()
flateReaderPool.Put(r.fr)
r.fr = nil
return err
}

1201
vendor/github.com/gorilla/websocket/conn.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

15
vendor/github.com/gorilla/websocket/conn_write.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "net"
func (c *Conn) writeBufs(bufs ...[]byte) error {
b := net.Buffers(bufs)
_, err := b.WriteTo(c.conn)
return err
}

View File

@ -0,0 +1,18 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
func (c *Conn) writeBufs(bufs ...[]byte) error {
for _, buf := range bufs {
if len(buf) > 0 {
if _, err := c.conn.Write(buf); err != nil {
return err
}
}
}
return nil
}

227
vendor/github.com/gorilla/websocket/doc.go generated vendored Normal file
View File

@ -0,0 +1,227 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package websocket implements the WebSocket protocol defined in RFC 6455.
//
// Overview
//
// The Conn type represents a WebSocket connection. A server application calls
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
//
// var upgrader = websocket.Upgrader{
// ReadBufferSize: 1024,
// WriteBufferSize: 1024,
// }
//
// func handler(w http.ResponseWriter, r *http.Request) {
// conn, err := upgrader.Upgrade(w, r, nil)
// if err != nil {
// log.Println(err)
// return
// }
// ... Use conn to send and receive messages.
// }
//
// Call the connection's WriteMessage and ReadMessage methods to send and
// receive messages as a slice of bytes. This snippet of code shows how to echo
// messages using these methods:
//
// for {
// messageType, p, err := conn.ReadMessage()
// if err != nil {
// log.Println(err)
// return
// }
// if err := conn.WriteMessage(messageType, p); err != nil {
// log.Println(err)
// return
// }
// }
//
// In above snippet of code, p is a []byte and messageType is an int with value
// websocket.BinaryMessage or websocket.TextMessage.
//
// An application can also send and receive messages using the io.WriteCloser
// and io.Reader interfaces. To send a message, call the connection NextWriter
// method to get an io.WriteCloser, write the message to the writer and close
// the writer when done. To receive a message, call the connection NextReader
// method to get an io.Reader and read until io.EOF is returned. This snippet
// shows how to echo messages using the NextWriter and NextReader methods:
//
// for {
// messageType, r, err := conn.NextReader()
// if err != nil {
// return
// }
// w, err := conn.NextWriter(messageType)
// if err != nil {
// return err
// }
// if _, err := io.Copy(w, r); err != nil {
// return err
// }
// if err := w.Close(); err != nil {
// return err
// }
// }
//
// Data Messages
//
// The WebSocket protocol distinguishes between text and binary data messages.
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
// binary messages is left to the application.
//
// This package uses the TextMessage and BinaryMessage integer constants to
// identify the two data message types. The ReadMessage and NextReader methods
// return the type of the received message. The messageType argument to the
// WriteMessage and NextWriter methods specifies the type of a sent message.
//
// It is the application's responsibility to ensure that text messages are
// valid UTF-8 encoded text.
//
// Control Messages
//
// The WebSocket protocol defines three types of control messages: close, ping
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
// methods to send a control message to the peer.
//
// Connections handle received close messages by calling the handler function
// set with the SetCloseHandler method and by returning a *CloseError from the
// NextReader, ReadMessage or the message Read method. The default close
// handler sends a close message to the peer.
//
// Connections handle received ping messages by calling the handler function
// set with the SetPingHandler method. The default ping handler sends a pong
// message to the peer.
//
// Connections handle received pong messages by calling the handler function
// set with the SetPongHandler method. The default pong handler does nothing.
// If an application sends ping messages, then the application should set a
// pong handler to receive the corresponding pong.
//
// The control message handler functions are called from the NextReader,
// ReadMessage and message reader Read methods. The default close and ping
// handlers can block these methods for a short time when the handler writes to
// the connection.
//
// The application must read the connection to process close, ping and pong
// messages sent from the peer. If the application is not otherwise interested
// in messages from the peer, then the application should start a goroutine to
// read and discard messages from the peer. A simple example is:
//
// func readLoop(c *websocket.Conn) {
// for {
// if _, _, err := c.NextReader(); err != nil {
// c.Close()
// break
// }
// }
// }
//
// Concurrency
//
// Connections support one concurrent reader and one concurrent writer.
//
// Applications are responsible for ensuring that no more than one goroutine
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
// that no more than one goroutine calls the read methods (NextReader,
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
// concurrently.
//
// The Close and WriteControl methods can be called concurrently with all other
// methods.
//
// Origin Considerations
//
// Web browsers allow Javascript applications to open a WebSocket connection to
// any host. It's up to the server to enforce an origin policy using the Origin
// request header sent by the browser.
//
// The Upgrader calls the function specified in the CheckOrigin field to check
// the origin. If the CheckOrigin function returns false, then the Upgrade
// method fails the WebSocket handshake with HTTP status 403.
//
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
// the handshake if the Origin request header is present and the Origin host is
// not equal to the Host request header.
//
// The deprecated package-level Upgrade function does not perform origin
// checking. The application is responsible for checking the Origin header
// before calling the Upgrade function.
//
// Buffers
//
// Connections buffer network input and output to reduce the number
// of system calls when reading or writing messages.
//
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
// Section 5 for a discussion of message framing. A WebSocket frame header is
// written to the network each time a write buffer is flushed to the network.
// Decreasing the size of the write buffer can increase the amount of framing
// overhead on the connection.
//
// The buffer sizes in bytes are specified by the ReadBufferSize and
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
// buffers created by the HTTP server when a buffer size field is set to zero.
// The HTTP server buffers have a size of 4096 at the time of this writing.
//
// The buffer sizes do not limit the size of a message that can be read or
// written by a connection.
//
// Buffers are held for the lifetime of the connection by default. If the
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
// write buffer only when writing a message.
//
// Applications should tune the buffer sizes to balance memory use and
// performance. Increasing the buffer size uses more memory, but can reduce the
// number of system calls to read or write the network. In the case of writing,
// increasing the buffer size can reduce the number of frame headers written to
// the network.
//
// Some guidelines for setting buffer parameters are:
//
// Limit the buffer sizes to the maximum expected message size. Buffers larger
// than the largest message do not provide any benefit.
//
// Depending on the distribution of message sizes, setting the buffer size to
// a value less than the maximum expected message size can greatly reduce memory
// use with a small impact on performance. Here's an example: If 99% of the
// messages are smaller than 256 bytes and the maximum message size is 512
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
// than a buffer size of 512 bytes. The memory savings is 50%.
//
// A write buffer pool is useful when the application has a modest number
// writes over a large number of connections. when buffers are pooled, a larger
// buffer size has a reduced impact on total memory use and has the benefit of
// reducing system calls and frame overhead.
//
// Compression EXPERIMENTAL
//
// Per message compression extensions (RFC 7692) are experimentally supported
// by this package in a limited capacity. Setting the EnableCompression option
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
// support.
//
// var upgrader = websocket.Upgrader{
// EnableCompression: true,
// }
//
// If compression was successfully negotiated with the connection's peer, any
// message received in compressed form will be automatically decompressed.
// All Read methods will return uncompressed bytes.
//
// Per message compression of messages written to a connection can be enabled
// or disabled by calling the corresponding Conn method:
//
// conn.EnableWriteCompression(false)
//
// Currently this package does not support compression with "context takeover".
// This means that messages must be compressed and decompressed in isolation,
// without retaining sliding window or dictionary state across messages. For
// more details refer to RFC 7692.
//
// Use of compression is experimental and may result in decreased performance.
package websocket

3
vendor/github.com/gorilla/websocket/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/gorilla/websocket
go 1.12

0
vendor/github.com/gorilla/websocket/go.sum generated vendored Normal file
View File

42
vendor/github.com/gorilla/websocket/join.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"io"
"strings"
)
// JoinMessages concatenates received messages to create a single io.Reader.
// The string term is appended to each message. The returned reader does not
// support concurrent calls to the Read method.
func JoinMessages(c *Conn, term string) io.Reader {
return &joinReader{c: c, term: term}
}
type joinReader struct {
c *Conn
term string
r io.Reader
}
func (r *joinReader) Read(p []byte) (int, error) {
if r.r == nil {
var err error
_, r.r, err = r.c.NextReader()
if err != nil {
return 0, err
}
if r.term != "" {
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
}
}
n, err := r.r.Read(p)
if err == io.EOF {
err = nil
r.r = nil
}
return n, err
}

60
vendor/github.com/gorilla/websocket/json.go generated vendored Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"encoding/json"
"io"
)
// WriteJSON writes the JSON encoding of v as a message.
//
// Deprecated: Use c.WriteJSON instead.
func WriteJSON(c *Conn, v interface{}) error {
return c.WriteJSON(v)
}
// WriteJSON writes the JSON encoding of v as a message.
//
// See the documentation for encoding/json Marshal for details about the
// conversion of Go values to JSON.
func (c *Conn) WriteJSON(v interface{}) error {
w, err := c.NextWriter(TextMessage)
if err != nil {
return err
}
err1 := json.NewEncoder(w).Encode(v)
err2 := w.Close()
if err1 != nil {
return err1
}
return err2
}
// ReadJSON reads the next JSON-encoded message from the connection and stores
// it in the value pointed to by v.
//
// Deprecated: Use c.ReadJSON instead.
func ReadJSON(c *Conn, v interface{}) error {
return c.ReadJSON(v)
}
// ReadJSON reads the next JSON-encoded message from the connection and stores
// it in the value pointed to by v.
//
// See the documentation for the encoding/json Unmarshal function for details
// about the conversion of JSON to a Go value.
func (c *Conn) ReadJSON(v interface{}) error {
_, r, err := c.NextReader()
if err != nil {
return err
}
err = json.NewDecoder(r).Decode(v)
if err == io.EOF {
// One value is expected in the message.
err = io.ErrUnexpectedEOF
}
return err
}

54
vendor/github.com/gorilla/websocket/mask.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
// +build !appengine
package websocket
import "unsafe"
const wordSize = int(unsafe.Sizeof(uintptr(0)))
func maskBytes(key [4]byte, pos int, b []byte) int {
// Mask one byte at a time for small buffers.
if len(b) < 2*wordSize {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
// Mask one byte at a time to word boundary.
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
n = wordSize - n
for i := range b[:n] {
b[i] ^= key[pos&3]
pos++
}
b = b[n:]
}
// Create aligned word size key.
var k [wordSize]byte
for i := range k {
k[i] = key[(pos+i)&3]
}
kw := *(*uintptr)(unsafe.Pointer(&k))
// Mask one word at a time.
n := (len(b) / wordSize) * wordSize
for i := 0; i < n; i += wordSize {
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
}
// Mask one byte at a time for remaining bytes.
b = b[n:]
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

15
vendor/github.com/gorilla/websocket/mask_safe.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
// +build appengine
package websocket
func maskBytes(key [4]byte, pos int, b []byte) int {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

102
vendor/github.com/gorilla/websocket/prepared.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bytes"
"net"
"sync"
"time"
)
// PreparedMessage caches on the wire representations of a message payload.
// Use PreparedMessage to efficiently send a message payload to multiple
// connections. PreparedMessage is especially useful when compression is used
// because the CPU and memory expensive compression operation can be executed
// once for a given set of compression options.
type PreparedMessage struct {
messageType int
data []byte
mu sync.Mutex
frames map[prepareKey]*preparedFrame
}
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
type prepareKey struct {
isServer bool
compress bool
compressionLevel int
}
// preparedFrame contains data in wire representation.
type preparedFrame struct {
once sync.Once
data []byte
}
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
// it to connection using WritePreparedMessage method. Valid wire
// representation will be calculated lazily only once for a set of current
// connection options.
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
pm := &PreparedMessage{
messageType: messageType,
frames: make(map[prepareKey]*preparedFrame),
data: data,
}
// Prepare a plain server frame.
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
if err != nil {
return nil, err
}
// To protect against caller modifying the data argument, remember the data
// copied to the plain server frame.
pm.data = frameData[len(frameData)-len(data):]
return pm, nil
}
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
pm.mu.Lock()
frame, ok := pm.frames[key]
if !ok {
frame = &preparedFrame{}
pm.frames[key] = frame
}
pm.mu.Unlock()
var err error
frame.once.Do(func() {
// Prepare a frame using a 'fake' connection.
// TODO: Refactor code in conn.go to allow more direct construction of
// the frame.
mu := make(chan struct{}, 1)
mu <- struct{}{}
var nc prepareConn
c := &Conn{
conn: &nc,
mu: mu,
isServer: key.isServer,
compressionLevel: key.compressionLevel,
enableWriteCompression: true,
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
}
if key.compress {
c.newCompressionWriter = compressNoContextTakeover
}
err = c.WriteMessage(pm.messageType, pm.data)
frame.data = nc.buf.Bytes()
})
return pm.messageType, frame.data, err
}
type prepareConn struct {
buf bytes.Buffer
net.Conn
}
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }

77
vendor/github.com/gorilla/websocket/proxy.go generated vendored Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"encoding/base64"
"errors"
"net"
"net/http"
"net/url"
"strings"
)
type netDialerFunc func(network, addr string) (net.Conn, error)
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
return fn(network, addr)
}
func init() {
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
})
}
type httpProxyDialer struct {
proxyURL *url.URL
forwardDial func(network, addr string) (net.Conn, error)
}
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
hostPort, _ := hostPortNoPort(hpd.proxyURL)
conn, err := hpd.forwardDial(network, hostPort)
if err != nil {
return nil, err
}
connectHeader := make(http.Header)
if user := hpd.proxyURL.User; user != nil {
proxyUser := user.Username()
if proxyPassword, passwordSet := user.Password(); passwordSet {
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
}
}
connectReq := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: addr},
Host: addr,
Header: connectHeader,
}
if err := connectReq.Write(conn); err != nil {
conn.Close()
return nil, err
}
// Read response. It's OK to use and discard buffered reader here becaue
// the remote server does not speak until spoken to.
br := bufio.NewReader(conn)
resp, err := http.ReadResponse(br, connectReq)
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != 200 {
conn.Close()
f := strings.SplitN(resp.Status, " ", 2)
return nil, errors.New(f[1])
}
return conn, nil
}

363
vendor/github.com/gorilla/websocket/server.go generated vendored Normal file
View File

@ -0,0 +1,363 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"errors"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// HandshakeError describes an error with the handshake from the peer.
type HandshakeError struct {
message string
}
func (e HandshakeError) Error() string { return e.message }
// Upgrader specifies parameters for upgrading an HTTP connection to a
// WebSocket connection.
type Upgrader struct {
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
// size is zero, then buffers allocated by the HTTP server are used. The
// I/O buffer sizes do not limit the size of the messages that can be sent
// or received.
ReadBufferSize, WriteBufferSize int
// WriteBufferPool is a pool of buffers for write operations. If the value
// is not set, then write buffers are allocated to the connection for the
// lifetime of the connection.
//
// A pool is most useful when the application has a modest volume of writes
// across a large number of connections.
//
// Applications should use a single pool for each unique value of
// WriteBufferSize.
WriteBufferPool BufferPool
// Subprotocols specifies the server's supported protocols in order of
// preference. If this field is not nil, then the Upgrade method negotiates a
// subprotocol by selecting the first match in this list with a protocol
// requested by the client. If there's no match, then no protocol is
// negotiated (the Sec-Websocket-Protocol header is not included in the
// handshake response).
Subprotocols []string
// Error specifies the function for generating HTTP error responses. If Error
// is nil, then http.Error is used to generate the HTTP response.
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
// CheckOrigin returns true if the request Origin header is acceptable. If
// CheckOrigin is nil, then a safe default is used: return false if the
// Origin request header is present and the origin host is not equal to
// request Host header.
//
// A CheckOrigin function should carefully validate the request origin to
// prevent cross-site request forgery.
CheckOrigin func(r *http.Request) bool
// EnableCompression specify if the server should attempt to negotiate per
// message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
}
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
err := HandshakeError{reason}
if u.Error != nil {
u.Error(w, r, status, err)
} else {
w.Header().Set("Sec-Websocket-Version", "13")
http.Error(w, http.StatusText(status), status)
}
return nil, err
}
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
func checkSameOrigin(r *http.Request) bool {
origin := r.Header["Origin"]
if len(origin) == 0 {
return true
}
u, err := url.Parse(origin[0])
if err != nil {
return false
}
return equalASCIIFold(u.Host, r.Host)
}
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
if u.Subprotocols != nil {
clientProtocols := Subprotocols(r)
for _, serverProtocol := range u.Subprotocols {
for _, clientProtocol := range clientProtocols {
if clientProtocol == serverProtocol {
return clientProtocol
}
}
}
} else if responseHeader != nil {
return responseHeader.Get("Sec-Websocket-Protocol")
}
return ""
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// application negotiated subprotocol (Sec-WebSocket-Protocol).
//
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response.
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
const badHandshake = "websocket: the client is not using the websocket protocol: "
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
}
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
}
if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
}
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
}
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
}
checkOrigin := u.CheckOrigin
if checkOrigin == nil {
checkOrigin = checkSameOrigin
}
if !checkOrigin(r) {
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")
}
challengeKey := r.Header.Get("Sec-Websocket-Key")
if challengeKey == "" {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")
}
subprotocol := u.selectSubprotocol(r, responseHeader)
// Negotiate PMCE
var compress bool
if u.EnableCompression {
for _, ext := range parseExtensions(r.Header) {
if ext[""] != "permessage-deflate" {
continue
}
compress = true
break
}
}
h, ok := w.(http.Hijacker)
if !ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
}
var brw *bufio.ReadWriter
netConn, brw, err := h.Hijack()
if err != nil {
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
}
if brw.Reader.Buffered() > 0 {
netConn.Close()
return nil, errors.New("websocket: client sent data before handshake is complete")
}
var br *bufio.Reader
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 {
// Reuse hijacked buffered reader as connection reader.
br = brw.Reader
}
buf := bufioWriterBuffer(netConn, brw.Writer)
var writeBuf []byte
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {
// Reuse hijacked write buffer as connection buffer.
writeBuf = buf
}
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf)
c.subprotocol = subprotocol
if compress {
c.newCompressionWriter = compressNoContextTakeover
c.newDecompressionReader = decompressNoContextTakeover
}
// Use larger of hijacked buffer and connection write buffer for header.
p := buf
if len(c.writeBuf) > len(p) {
p = c.writeBuf
}
p = p[:0]
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
p = append(p, computeAcceptKey(challengeKey)...)
p = append(p, "\r\n"...)
if c.subprotocol != "" {
p = append(p, "Sec-WebSocket-Protocol: "...)
p = append(p, c.subprotocol...)
p = append(p, "\r\n"...)
}
if compress {
p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
}
for k, vs := range responseHeader {
if k == "Sec-Websocket-Protocol" {
continue
}
for _, v := range vs {
p = append(p, k...)
p = append(p, ": "...)
for i := 0; i < len(v); i++ {
b := v[i]
if b <= 31 {
// prevent response splitting.
b = ' '
}
p = append(p, b)
}
p = append(p, "\r\n"...)
}
}
p = append(p, "\r\n"...)
// Clear deadlines set by HTTP server.
netConn.SetDeadline(time.Time{})
if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
}
if _, err = netConn.Write(p); err != nil {
netConn.Close()
return nil, err
}
if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Time{})
}
return c, nil
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// Deprecated: Use websocket.Upgrader instead.
//
// Upgrade does not perform origin checking. The application is responsible for
// checking the Origin header before calling Upgrade. An example implementation
// of the same origin policy check is:
//
// if req.Header.Get("Origin") != "http://"+req.Host {
// http.Error(w, "Origin not allowed", http.StatusForbidden)
// return
// }
//
// If the endpoint supports subprotocols, then the application is responsible
// for negotiating the protocol used on the connection. Use the Subprotocols()
// function to get the subprotocols requested by the client. Use the
// Sec-Websocket-Protocol response header to specify the subprotocol selected
// by the application.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// negotiated subprotocol (Sec-Websocket-Protocol).
//
// The connection buffers IO to the underlying network connection. The
// readBufSize and writeBufSize parameters specify the size of the buffers to
// use. Messages can be larger than the buffers.
//
// If the request is not a valid WebSocket handshake, then Upgrade returns an
// error of type HandshakeError. Applications should handle this error by
// replying to the client with an HTTP error response.
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
// don't return errors to maintain backwards compatibility
}
u.CheckOrigin = func(r *http.Request) bool {
// allow all connections by default
return true
}
return u.Upgrade(w, r, responseHeader)
}
// Subprotocols returns the subprotocols requested by the client in the
// Sec-Websocket-Protocol header.
func Subprotocols(r *http.Request) []string {
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
if h == "" {
return nil
}
protocols := strings.Split(h, ",")
for i := range protocols {
protocols[i] = strings.TrimSpace(protocols[i])
}
return protocols
}
// IsWebSocketUpgrade returns true if the client requested upgrade to the
// WebSocket protocol.
func IsWebSocketUpgrade(r *http.Request) bool {
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
tokenListContainsValue(r.Header, "Upgrade", "websocket")
}
// bufioReaderSize size returns the size of a bufio.Reader.
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int {
// This code assumes that peek on a reset reader returns
// bufio.Reader.buf[:0].
// TODO: Use bufio.Reader.Size() after Go 1.10
br.Reset(originalReader)
if p, err := br.Peek(0); err == nil {
return cap(p)
}
return 0
}
// writeHook is an io.Writer that records the last slice passed to it vio
// io.Writer.Write.
type writeHook struct {
p []byte
}
func (wh *writeHook) Write(p []byte) (int, error) {
wh.p = p
return len(p), nil
}
// bufioWriterBuffer grabs the buffer from a bufio.Writer.
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
// This code assumes that bufio.Writer.buf[:1] is passed to the
// bufio.Writer's underlying writer.
var wh writeHook
bw.Reset(&wh)
bw.WriteByte(0)
bw.Flush()
bw.Reset(originalWriter)
return wh.p[:cap(wh.p)]
}

19
vendor/github.com/gorilla/websocket/trace.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
// +build go1.8
package websocket
import (
"crypto/tls"
"net/http/httptrace"
)
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
if trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
err := doHandshake(tlsConn, cfg)
if trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
}
return err
}

12
vendor/github.com/gorilla/websocket/trace_17.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
// +build !go1.8
package websocket
import (
"crypto/tls"
"net/http/httptrace"
)
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
return doHandshake(tlsConn, cfg)
}

283
vendor/github.com/gorilla/websocket/util.go generated vendored Normal file
View File

@ -0,0 +1,283 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"io"
"net/http"
"strings"
"unicode/utf8"
)
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
func computeAcceptKey(challengeKey string) string {
h := sha1.New()
h.Write([]byte(challengeKey))
h.Write(keyGUID)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func generateChallengeKey() (string, error) {
p := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, p); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(p), nil
}
// Token octets per RFC 2616.
var isTokenOctet = [256]bool{
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}
// skipSpace returns a slice of the string s with all leading RFC 2616 linear
// whitespace removed.
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if b := s[i]; b != ' ' && b != '\t' {
break
}
}
return s[i:]
}
// nextToken returns the leading RFC 2616 token of s and the string following
// the token.
func nextToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if !isTokenOctet[s[i]] {
break
}
}
return s[:i], s[i:]
}
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
// and the string following the token or quoted string.
func nextTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return nextToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j++
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j++
}
}
return "", ""
}
}
return "", ""
}
// equalASCIIFold returns true if s is equal to t with ASCII case folding as
// defined in RFC 4790.
func equalASCIIFold(s, t string) bool {
for s != "" && t != "" {
sr, size := utf8.DecodeRuneInString(s)
s = s[size:]
tr, size := utf8.DecodeRuneInString(t)
t = t[size:]
if sr == tr {
continue
}
if 'A' <= sr && sr <= 'Z' {
sr = sr + 'a' - 'A'
}
if 'A' <= tr && tr <= 'Z' {
tr = tr + 'a' - 'A'
}
if sr != tr {
return false
}
}
return s == t
}
// tokenListContainsValue returns true if the 1#token header with the given
// name contains a token equal to value with ASCII case folding.
func tokenListContainsValue(header http.Header, name string, value string) bool {
headers:
for _, s := range header[name] {
for {
var t string
t, s = nextToken(skipSpace(s))
if t == "" {
continue headers
}
s = skipSpace(s)
if s != "" && s[0] != ',' {
continue headers
}
if equalASCIIFold(t, value) {
return true
}
if s == "" {
continue headers
}
s = s[1:]
}
}
return false
}
// parseExtensions parses WebSocket extensions from a header.
func parseExtensions(header http.Header) []map[string]string {
// From RFC 6455:
//
// Sec-WebSocket-Extensions = extension-list
// extension-list = 1#extension
// extension = extension-token *( ";" extension-param )
// extension-token = registered-token
// registered-token = token
// extension-param = token [ "=" (token | quoted-string) ]
// ;When using the quoted-string syntax variant, the value
// ;after quoted-string unescaping MUST conform to the
// ;'token' ABNF.
var result []map[string]string
headers:
for _, s := range header["Sec-Websocket-Extensions"] {
for {
var t string
t, s = nextToken(skipSpace(s))
if t == "" {
continue headers
}
ext := map[string]string{"": t}
for {
s = skipSpace(s)
if !strings.HasPrefix(s, ";") {
break
}
var k string
k, s = nextToken(skipSpace(s[1:]))
if k == "" {
continue headers
}
s = skipSpace(s)
var v string
if strings.HasPrefix(s, "=") {
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
s = skipSpace(s)
}
if s != "" && s[0] != ',' && s[0] != ';' {
continue headers
}
ext[k] = v
}
if s != "" && s[0] != ',' {
continue headers
}
result = append(result, ext)
if s == "" {
continue headers
}
s = s[1:]
}
}
return result
}

473
vendor/github.com/gorilla/websocket/x_net_proxy.go generated vendored Normal file
View File

@ -0,0 +1,473 @@
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
// Package proxy provides support for a variety of protocols to proxy network
// data.
//
package websocket
import (
"errors"
"io"
"net"
"net/url"
"os"
"strconv"
"strings"
"sync"
)
type proxy_direct struct{}
// Direct is a direct proxy: one that makes network connections directly.
var proxy_Direct = proxy_direct{}
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}
// A PerHost directs connections to a default Dialer unless the host name
// requested matches one of a number of exceptions.
type proxy_PerHost struct {
def, bypass proxy_Dialer
bypassNetworks []*net.IPNet
bypassIPs []net.IP
bypassZones []string
bypassHosts []string
}
// NewPerHost returns a PerHost Dialer that directs connections to either
// defaultDialer or bypass, depending on whether the connection matches one of
// the configured rules.
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
return &proxy_PerHost{
def: defaultDialer,
bypass: bypass,
}
}
// Dial connects to the address addr on the given network through either
// defaultDialer or bypass.
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return p.dialerForRequest(host).Dial(network, addr)
}
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
if ip := net.ParseIP(host); ip != nil {
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
}
}
for _, bypassIP := range p.bypassIPs {
if bypassIP.Equal(ip) {
return p.bypass
}
}
return p.def
}
for _, zone := range p.bypassZones {
if strings.HasSuffix(host, zone) {
return p.bypass
}
if host == zone[1:] {
// For a zone ".example.com", we match "example.com"
// too.
return p.bypass
}
}
for _, bypassHost := range p.bypassHosts {
if bypassHost == host {
return p.bypass
}
}
return p.def
}
// AddFromString parses a string that contains comma-separated values
// specifying hosts that should use the bypass proxy. Each value is either an
// IP address, a CIDR range, a zone (*.example.com) or a host name
// (localhost). A best effort is made to parse the string and errors are
// ignored.
func (p *proxy_PerHost) AddFromString(s string) {
hosts := strings.Split(s, ",")
for _, host := range hosts {
host = strings.TrimSpace(host)
if len(host) == 0 {
continue
}
if strings.Contains(host, "/") {
// We assume that it's a CIDR address like 127.0.0.0/8
if _, net, err := net.ParseCIDR(host); err == nil {
p.AddNetwork(net)
}
continue
}
if ip := net.ParseIP(host); ip != nil {
p.AddIP(ip)
continue
}
if strings.HasPrefix(host, "*.") {
p.AddZone(host[1:])
continue
}
p.AddHost(host)
}
}
// AddIP specifies an IP address that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match an IP.
func (p *proxy_PerHost) AddIP(ip net.IP) {
p.bypassIPs = append(p.bypassIPs, ip)
}
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match.
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
p.bypassNetworks = append(p.bypassNetworks, net)
}
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
// "example.com" matches "example.com" and all of its subdomains.
func (p *proxy_PerHost) AddZone(zone string) {
if strings.HasSuffix(zone, ".") {
zone = zone[:len(zone)-1]
}
if !strings.HasPrefix(zone, ".") {
zone = "." + zone
}
p.bypassZones = append(p.bypassZones, zone)
}
// AddHost specifies a host name that will use the bypass proxy.
func (p *proxy_PerHost) AddHost(host string) {
if strings.HasSuffix(host, ".") {
host = host[:len(host)-1]
}
p.bypassHosts = append(p.bypassHosts, host)
}
// A Dialer is a means to establish a connection.
type proxy_Dialer interface {
// Dial connects to the given address via the proxy.
Dial(network, addr string) (c net.Conn, err error)
}
// Auth contains authentication parameters that specific Dialers may require.
type proxy_Auth struct {
User, Password string
}
// FromEnvironment returns the dialer specified by the proxy related variables in
// the environment.
func proxy_FromEnvironment() proxy_Dialer {
allProxy := proxy_allProxyEnv.Get()
if len(allProxy) == 0 {
return proxy_Direct
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return proxy_Direct
}
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
if err != nil {
return proxy_Direct
}
noProxy := proxy_noProxyEnv.Get()
if len(noProxy) == 0 {
return proxy
}
perHost := proxy_NewPerHost(proxy, proxy_Direct)
perHost.AddFromString(noProxy)
return perHost
}
// proxySchemes is a map from URL schemes to a function that creates a Dialer
// from a URL with such a scheme.
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
if proxy_proxySchemes == nil {
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
}
proxy_proxySchemes[scheme] = f
}
// FromURL returns a Dialer given a URL specification and an underlying
// Dialer for it to make network requests.
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
var auth *proxy_Auth
if u.User != nil {
auth = new(proxy_Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
switch u.Scheme {
case "socks5":
return proxy_SOCKS5("tcp", u.Host, auth, forward)
}
// If the scheme doesn't match any of the built-in schemes, see if it
// was registered by another package.
if proxy_proxySchemes != nil {
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
return f(u, forward)
}
}
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
}
var (
proxy_allProxyEnv = &proxy_envOnce{
names: []string{"ALL_PROXY", "all_proxy"},
}
proxy_noProxyEnv = &proxy_envOnce{
names: []string{"NO_PROXY", "no_proxy"},
}
)
// envOnce looks up an environment variable (optionally by multiple
// names) once. It mitigates expensive lookups on some platforms
// (e.g. Windows).
// (Borrowed from net/http/transport.go)
type proxy_envOnce struct {
names []string
once sync.Once
val string
}
func (e *proxy_envOnce) Get() string {
e.once.Do(e.init)
return e.val
}
func (e *proxy_envOnce) init() {
for _, n := range e.names {
e.val = os.Getenv(n)
if e.val != "" {
return
}
}
}
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928 and RFC 1929.
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
s := &proxy_socks5{
network: network,
addr: addr,
forward: forward,
}
if auth != nil {
s.user = auth.User
s.password = auth.Password
}
return s, nil
}
type proxy_socks5 struct {
user, password string
network, addr string
forward proxy_Dialer
}
const proxy_socks5Version = 5
const (
proxy_socks5AuthNone = 0
proxy_socks5AuthPassword = 2
)
const proxy_socks5Connect = 1
const (
proxy_socks5IP4 = 1
proxy_socks5Domain = 3
proxy_socks5IP6 = 4
)
var proxy_socks5Errors = []string{
"",
"general failure",
"connection forbidden",
"network unreachable",
"host unreachable",
"connection refused",
"TTL expired",
"command not supported",
"address type not supported",
}
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
}
conn, err := s.forward.Dial(s.network, s.addr)
if err != nil {
return nil, err
}
if err := s.connect(conn, addr); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
// connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target,
// which must be a canonical address with a host and port.
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
host, portStr, err := net.SplitHostPort(target)
if err != nil {
return err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, proxy_socks5Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
} else {
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
}
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[0] != 5 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
}
if buf[1] == 0xff {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
// See RFC 1929
if buf[1] == proxy_socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)
buf = append(buf, uint8(len(s.user)))
buf = append(buf, s.user...)
buf = append(buf, uint8(len(s.password)))
buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[1] != 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
}
}
buf = buf[:0]
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, proxy_socks5IP4)
ip = ip4
} else {
buf = append(buf, proxy_socks5IP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return errors.New("proxy: destination host name too long: " + host)
}
buf = append(buf, proxy_socks5Domain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
failure := "unknown error"
if int(buf[1]) < len(proxy_socks5Errors) {
failure = proxy_socks5Errors[buf[1]]
}
if len(failure) > 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}
bytesToDiscard := 0
switch buf[3] {
case proxy_socks5IP4:
bytesToDiscard = net.IPv4len
case proxy_socks5IP6:
bytesToDiscard = net.IPv6len
case proxy_socks5Domain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
return nil
}

3
vendor/modules.txt vendored
View File

@ -12,6 +12,9 @@ github.com/go-ldap/ldap/v3
# github.com/go-sql-driver/mysql v1.5.0 # github.com/go-sql-driver/mysql v1.5.0
## explicit ## explicit
github.com/go-sql-driver/mysql github.com/go-sql-driver/mysql
# github.com/gorilla/websocket v1.4.2
## explicit
github.com/gorilla/websocket
# github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 # github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940
## explicit ## explicit
github.com/goshuirc/e-nfa github.com/goshuirc/e-nfa