]> Sergey Matveev's repositories - btrtrc.git/blob - tracker/http/http.go
Rename tracker/http package
[btrtrc.git] / tracker / http / http.go
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) != 0 {
137                 vars.Add("http responses with nonempty peers key", 1)
138         }
139         ret.Peers = trackerResponse.Peers
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 }