frontend/http: add request sanitization
This commit is contained in:
parent
b7e6719129
commit
6dee48ce17
2 changed files with 58 additions and 33 deletions
|
@ -65,11 +65,10 @@ type Config struct {
|
||||||
Addr string `yaml:"addr"`
|
Addr string `yaml:"addr"`
|
||||||
ReadTimeout time.Duration `yaml:"read_timeout"`
|
ReadTimeout time.Duration `yaml:"read_timeout"`
|
||||||
WriteTimeout time.Duration `yaml:"write_timeout"`
|
WriteTimeout time.Duration `yaml:"write_timeout"`
|
||||||
AllowIPSpoofing bool `yaml:"allow_ip_spoofing"`
|
|
||||||
RealIPHeader string `yaml:"real_ip_header"`
|
|
||||||
TLSCertPath string `yaml:"tls_cert_path"`
|
TLSCertPath string `yaml:"tls_cert_path"`
|
||||||
TLSKeyPath string `yaml:"tls_key_path"`
|
TLSKeyPath string `yaml:"tls_key_path"`
|
||||||
EnableRequestTiming bool `yaml:"enable_request_timing"`
|
EnableRequestTiming bool `yaml:"enable_request_timing"`
|
||||||
|
ParseOptions `yaml:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogFields renders the current config as a set of Logrus fields.
|
// LogFields renders the current config as a set of Logrus fields.
|
||||||
|
@ -78,11 +77,14 @@ func (cfg Config) LogFields() log.Fields {
|
||||||
"addr": cfg.Addr,
|
"addr": cfg.Addr,
|
||||||
"readTimeout": cfg.ReadTimeout,
|
"readTimeout": cfg.ReadTimeout,
|
||||||
"writeTimeout": cfg.WriteTimeout,
|
"writeTimeout": cfg.WriteTimeout,
|
||||||
"allowIPSpoofing": cfg.AllowIPSpoofing,
|
|
||||||
"realIPHeader": cfg.RealIPHeader,
|
|
||||||
"tlsCertPath": cfg.TLSCertPath,
|
"tlsCertPath": cfg.TLSCertPath,
|
||||||
"tlsKeyPath": cfg.TLSKeyPath,
|
"tlsKeyPath": cfg.TLSKeyPath,
|
||||||
"enableRequestTiming": cfg.EnableRequestTiming,
|
"enableRequestTiming": cfg.EnableRequestTiming,
|
||||||
|
"allowIPSpoofing": cfg.AllowIPSpoofing,
|
||||||
|
"realIPHeader": cfg.RealIPHeader,
|
||||||
|
"maxNumWant": cfg.MaxNumWant,
|
||||||
|
"defaultNumWant": cfg.DefaultNumWant,
|
||||||
|
"maxScrapeInfohashes": cfg.MaxScrapeInfoHashes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +221,7 @@ func (f *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httpr
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
req, err := ParseAnnounce(r, f.RealIPHeader, f.AllowIPSpoofing)
|
req, err := ParseAnnounce(r, f.ParseOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
|
@ -258,7 +260,7 @@ func (f *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
req, err := ParseScrape(r)
|
req, err := ParseScrape(r, f.ParseOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -7,12 +7,19 @@ import (
|
||||||
"github.com/chihaya/chihaya/bittorrent"
|
"github.com/chihaya/chihaya/bittorrent"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request.
|
// ParseOptions is the configuration used to parse an Announce Request.
|
||||||
//
|
//
|
||||||
// If allowIPSpoofing is true, IPs provided via params will be used.
|
// If AllowIPSpoofing is true, IPs provided via BitTorrent params will be used.
|
||||||
// If realIPHeader is not empty string, the first value of the HTTP Header with
|
// If RealIPHeader is not empty string, the value of the first HTTP Header with
|
||||||
// that name will be used.
|
// that name will be used.
|
||||||
func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) (*bittorrent.AnnounceRequest, error) {
|
type ParseOptions struct {
|
||||||
|
AllowIPSpoofing bool `yaml:"allowIPSpoofing"`
|
||||||
|
RealIPHeader string `yaml:"realIPHeader"`
|
||||||
|
bittorrent.RequestSanitizer `yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request.
|
||||||
|
func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequest, error) {
|
||||||
qp, err := bittorrent.ParseURLData(r.RequestURI)
|
qp, err := bittorrent.ParseURLData(r.RequestURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -20,15 +27,23 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) (
|
||||||
|
|
||||||
request := &bittorrent.AnnounceRequest{Params: qp}
|
request := &bittorrent.AnnounceRequest{Params: qp}
|
||||||
|
|
||||||
eventStr, _ := qp.String("event")
|
// Attempt to parse the event from the request.
|
||||||
request.Event, err = bittorrent.NewEvent(eventStr)
|
var eventStr string
|
||||||
if err != nil {
|
eventStr, request.EventProvided = qp.String("event")
|
||||||
return nil, bittorrent.ClientError("failed to provide valid client event")
|
if request.EventProvided {
|
||||||
|
request.Event, err = bittorrent.NewEvent(eventStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, bittorrent.ClientError("failed to provide valid client event")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
request.Event = bittorrent.None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if the client expects a compact response.
|
||||||
compactStr, _ := qp.String("compact")
|
compactStr, _ := qp.String("compact")
|
||||||
request.Compact = compactStr != "" && compactStr != "0"
|
request.Compact = compactStr != "" && compactStr != "0"
|
||||||
|
|
||||||
|
// Parse the infohash from the request.
|
||||||
infoHashes := qp.InfoHashes()
|
infoHashes := qp.InfoHashes()
|
||||||
if len(infoHashes) < 1 {
|
if len(infoHashes) < 1 {
|
||||||
return nil, bittorrent.ClientError("no info_hash parameter supplied")
|
return nil, bittorrent.ClientError("no info_hash parameter supplied")
|
||||||
|
@ -38,6 +53,7 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) (
|
||||||
}
|
}
|
||||||
request.InfoHash = infoHashes[0]
|
request.InfoHash = infoHashes[0]
|
||||||
|
|
||||||
|
// Parse the PeerID from the request.
|
||||||
peerID, ok := qp.String("peer_id")
|
peerID, ok := qp.String("peer_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, bittorrent.ClientError("failed to parse parameter: peer_id")
|
return nil, bittorrent.ClientError("failed to parse parameter: peer_id")
|
||||||
|
@ -47,43 +63,54 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) (
|
||||||
}
|
}
|
||||||
request.Peer.ID = bittorrent.PeerIDFromString(peerID)
|
request.Peer.ID = bittorrent.PeerIDFromString(peerID)
|
||||||
|
|
||||||
|
// Determine the number of remaining pieces for the client.
|
||||||
request.Left, err = qp.Uint64("left")
|
request.Left, err = qp.Uint64("left")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, bittorrent.ClientError("failed to parse parameter: left")
|
return nil, bittorrent.ClientError("failed to parse parameter: left")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the number of pieces downloaded by the client.
|
||||||
request.Downloaded, err = qp.Uint64("downloaded")
|
request.Downloaded, err = qp.Uint64("downloaded")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, bittorrent.ClientError("failed to parse parameter: downloaded")
|
return nil, bittorrent.ClientError("failed to parse parameter: downloaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the number of pieces shared by the client.
|
||||||
request.Uploaded, err = qp.Uint64("uploaded")
|
request.Uploaded, err = qp.Uint64("uploaded")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, bittorrent.ClientError("failed to parse parameter: uploaded")
|
return nil, bittorrent.ClientError("failed to parse parameter: uploaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the number of peers the client wants in the response.
|
||||||
numwant, err := qp.Uint64("numwant")
|
numwant, err := qp.Uint64("numwant")
|
||||||
if err != nil && err != bittorrent.ErrKeyNotFound {
|
if err != nil && err != bittorrent.ErrKeyNotFound {
|
||||||
return nil, bittorrent.ClientError("failed to parse parameter: numwant")
|
return nil, bittorrent.ClientError("failed to parse parameter: numwant")
|
||||||
}
|
}
|
||||||
request.NumWant = uint32(numwant)
|
request.NumWant = uint32(numwant)
|
||||||
|
request.NumWantProvided = err == nil
|
||||||
|
|
||||||
|
// Parse the port where the client is listening.
|
||||||
port, err := qp.Uint64("port")
|
port, err := qp.Uint64("port")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, bittorrent.ClientError("failed to parse parameter: port")
|
return nil, bittorrent.ClientError("failed to parse parameter: port")
|
||||||
}
|
}
|
||||||
request.Peer.Port = uint16(port)
|
request.Peer.Port = uint16(port)
|
||||||
|
|
||||||
request.Peer.IP.IP = requestedIP(r, qp, realIPHeader, allowIPSpoofing)
|
// Parse the IP address where the client is listening.
|
||||||
|
request.Peer.IP.IP, request.IPProvided = requestedIP(r, qp, opts)
|
||||||
if request.Peer.IP.IP == nil {
|
if request.Peer.IP.IP == nil {
|
||||||
return nil, bittorrent.ClientError("failed to parse peer IP address")
|
return nil, bittorrent.ClientError("failed to parse peer IP address")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := opts.SanitizeAnnounce(request); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseScrape parses an bittorrent.ScrapeRequest from an http.Request.
|
// ParseScrape parses an bittorrent.ScrapeRequest from an http.Request.
|
||||||
func ParseScrape(r *http.Request) (*bittorrent.ScrapeRequest, error) {
|
func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error) {
|
||||||
qp, err := bittorrent.ParseURLData(r.RequestURI)
|
qp, err := bittorrent.ParseURLData(r.RequestURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -99,39 +126,35 @@ func ParseScrape(r *http.Request) (*bittorrent.ScrapeRequest, error) {
|
||||||
Params: qp,
|
Params: qp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := opts.SanitizeScrape(request); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestedIP determines the IP address for a BitTorrent client request.
|
// requestedIP determines the IP address for a BitTorrent client request.
|
||||||
//
|
func requestedIP(r *http.Request, p bittorrent.Params, opts ParseOptions) (ip net.IP, provided bool) {
|
||||||
// If allowIPSpoofing is true, IPs provided via params will be used.
|
if opts.AllowIPSpoofing {
|
||||||
// If realIPHeader is not empty string, the first value of the HTTP Header with
|
|
||||||
// that name will be used.
|
|
||||||
func requestedIP(r *http.Request, p bittorrent.Params, realIPHeader string, allowIPSpoofing bool) net.IP {
|
|
||||||
if allowIPSpoofing {
|
|
||||||
if ipstr, ok := p.String("ip"); ok {
|
if ipstr, ok := p.String("ip"); ok {
|
||||||
ip := net.ParseIP(ipstr)
|
return net.ParseIP(ipstr), true
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipstr, ok := p.String("ipv4"); ok {
|
if ipstr, ok := p.String("ipv4"); ok {
|
||||||
ip := net.ParseIP(ipstr)
|
return net.ParseIP(ipstr), true
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipstr, ok := p.String("ipv6"); ok {
|
if ipstr, ok := p.String("ipv6"); ok {
|
||||||
ip := net.ParseIP(ipstr)
|
return net.ParseIP(ipstr), true
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if realIPHeader != "" {
|
if opts.RealIPHeader != "" {
|
||||||
if ips, ok := r.Header[realIPHeader]; ok && len(ips) > 0 {
|
if ips, ok := r.Header[opts.RealIPHeader]; ok && len(ips) > 0 {
|
||||||
ip := net.ParseIP(ips[0])
|
return net.ParseIP(ips[0]), false
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||||
return net.ParseIP(host)
|
return net.ParseIP(host), false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue