github.com/anacrolix/torrent@v1.61.0/tracker/http/http.go (about)

     1  package httpTracker
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"expvar"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"net"
    11  	"net/http"
    12  	"net/url"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/anacrolix/missinggo/httptoo"
    17  
    18  	"github.com/anacrolix/torrent/bencode"
    19  	"github.com/anacrolix/torrent/tracker/shared"
    20  	"github.com/anacrolix/torrent/tracker/udp"
    21  	"github.com/anacrolix/torrent/version"
    22  )
    23  
    24  var vars = expvar.NewMap("tracker/http")
    25  
    26  func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts AnnounceOpt) {
    27  	q := url.Values{}
    28  
    29  	q.Set("key", strconv.FormatInt(int64(ar.Key), 10))
    30  	q.Set("info_hash", string(ar.InfoHash[:]))
    31  	q.Set("peer_id", string(ar.PeerId[:]))
    32  	// AFAICT, port is mandatory, and there's no implied port key.
    33  	q.Set("port", fmt.Sprintf("%d", ar.Port))
    34  	q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
    35  	q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
    36  
    37  	// The AWS S3 tracker returns "400 Bad Request: left(-1) was not in the valid range 0 -
    38  	// 9223372036854775807" if left is out of range, or "500 Internal Server Error: Internal Server
    39  	// Error" if omitted entirely.
    40  	left := ar.Left
    41  	if left < 0 {
    42  		left = math.MaxInt64
    43  	}
    44  	q.Set("left", strconv.FormatInt(left, 10))
    45  
    46  	if ar.Event != shared.None {
    47  		q.Set("event", ar.Event.String())
    48  	}
    49  	// http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol
    50  	q.Set("compact", "1")
    51  	// According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO:
    52  	// Take EncryptionPolicy or something like it as a parameter.
    53  	q.Set("supportcrypto", "1")
    54  	doIp := func(versionKey string, ip net.IP) {
    55  		if ip == nil {
    56  			return
    57  		}
    58  		ipString := ip.String()
    59  		q.Set(versionKey, ipString)
    60  		// Let's try listing them. BEP 3 mentions having an "ip" param, and BEP 7 says we can list
    61  		// addresses for other address-families, although it's not encouraged.
    62  		q.Add("ip", ipString)
    63  	}
    64  	doIp("ipv4", opts.ClientIp4)
    65  	doIp("ipv6", opts.ClientIp6)
    66  	// We're operating purely on query-escaped strings, where + would have already been encoded to
    67  	// %2B, and + has no other special meaning. See https://github.com/anacrolix/torrent/issues/534.
    68  	qstr := strings.ReplaceAll(q.Encode(), "+", "%20")
    69  
    70  	// Some private trackers require the original query param to be in the first position.
    71  	if _url.RawQuery != "" {
    72  		_url.RawQuery += "&" + qstr
    73  	} else {
    74  		_url.RawQuery = qstr
    75  	}
    76  }
    77  
    78  type AnnounceOpt struct {
    79  	UserAgent           string
    80  	HostHeader          string
    81  	ClientIp4           net.IP
    82  	ClientIp6           net.IP
    83  	HttpRequestDirector func(*http.Request) error
    84  }
    85  
    86  type AnnounceRequest = udp.AnnounceRequest
    87  
    88  func (cl Client) Announce(ctx context.Context, ar AnnounceRequest, opt AnnounceOpt) (ret AnnounceResponse, err error) {
    89  	_url := httptoo.CopyURL(cl.url_)
    90  	setAnnounceParams(_url, &ar, opt)
    91  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url.String(), nil)
    92  	userAgent := opt.UserAgent
    93  	if userAgent == "" {
    94  		userAgent = version.DefaultHttpUserAgent
    95  	}
    96  	if userAgent != "" {
    97  		req.Header.Set("User-Agent", userAgent)
    98  	}
    99  
   100  	if opt.HttpRequestDirector != nil {
   101  		err = opt.HttpRequestDirector(req)
   102  		if err != nil {
   103  			err = fmt.Errorf("error modifying HTTP request: %w", err)
   104  			return
   105  		}
   106  	}
   107  
   108  	req.Host = opt.HostHeader
   109  	resp, err := cl.hc.Do(req)
   110  	if err != nil {
   111  		return
   112  	}
   113  	defer resp.Body.Close()
   114  	var buf bytes.Buffer
   115  	io.Copy(&buf, resp.Body)
   116  	if resp.StatusCode != 200 {
   117  		err = fmt.Errorf("response from tracker: %s: %q", resp.Status, buf.Bytes())
   118  		return
   119  	}
   120  	var trackerResponse HttpResponse
   121  	err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
   122  	if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
   123  		err = nil
   124  	} else if err != nil {
   125  		err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
   126  		return
   127  	}
   128  	if trackerResponse.FailureReason != "" {
   129  		err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason)
   130  		return
   131  	}
   132  	vars.Add("successful http announces", 1)
   133  	ret.Interval = trackerResponse.Interval
   134  	ret.Leechers = trackerResponse.Incomplete
   135  	ret.Seeders = trackerResponse.Complete
   136  	if len(trackerResponse.Peers.List) != 0 {
   137  		vars.Add("http responses with nonempty peers key", 1)
   138  	}
   139  	ret.Peers = trackerResponse.Peers.List
   140  	if len(trackerResponse.Peers6) != 0 {
   141  		vars.Add("http responses with nonempty peers6 key", 1)
   142  	}
   143  	for _, na := range trackerResponse.Peers6 {
   144  		ret.Peers = append(ret.Peers, Peer{
   145  			IP:   na.IP,
   146  			Port: na.Port,
   147  		})
   148  	}
   149  	return
   150  }
   151  
   152  type AnnounceResponse struct {
   153  	Interval int32 // Minimum seconds the local peer should wait before next announce.
   154  	Leechers int32
   155  	Seeders  int32
   156  	Peers    []Peer
   157  }