]> Sergey Matveev's repositories - btrtrc.git/blob - tracker/http.go
16182f0ec5125e7d45642e9aca2b843deb45b422
[btrtrc.git] / tracker / http.go
1 package tracker
2
3 import (
4         "bytes"
5         "fmt"
6         "io"
7         "net/http"
8         "net/url"
9         "strconv"
10
11         "github.com/anacrolix/dht/krpc"
12         "github.com/anacrolix/missinggo/httptoo"
13
14         "github.com/anacrolix/torrent/bencode"
15 )
16
17 type httpResponse struct {
18         FailureReason string `bencode:"failure reason"`
19         Interval      int32  `bencode:"interval"`
20         TrackerId     string `bencode:"tracker id"`
21         Complete      int32  `bencode:"complete"`
22         Incomplete    int32  `bencode:"incomplete"`
23         Peers         Peers  `bencode:"peers"`
24         // BEP 7
25         Peers6 krpc.CompactIPv6NodeAddrs `bencode:"peers6"`
26 }
27
28 type Peers []Peer
29
30 func (me *Peers) UnmarshalBencode(b []byte) (err error) {
31         var _v interface{}
32         err = bencode.Unmarshal(b, &_v)
33         if err != nil {
34                 return
35         }
36         switch v := _v.(type) {
37         case string:
38                 vars.Add("http responses with string peers", 1)
39                 var cnas krpc.CompactIPv4NodeAddrs
40                 err = cnas.UnmarshalBinary([]byte(v))
41                 if err != nil {
42                         return
43                 }
44                 for _, cp := range cnas {
45                         *me = append(*me, Peer{
46                                 IP:   cp.IP[:],
47                                 Port: int(cp.Port),
48                         })
49                 }
50                 return
51         case []interface{}:
52                 vars.Add("http responses with list peers", 1)
53                 for _, i := range v {
54                         var p Peer
55                         p.fromDictInterface(i.(map[string]interface{}))
56                         *me = append(*me, p)
57                 }
58                 return
59         default:
60                 vars.Add("http responses with unhandled peers type", 1)
61                 err = fmt.Errorf("unsupported type: %T", _v)
62                 return
63         }
64 }
65
66 func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts Announce) {
67         q := _url.Query()
68
69         q.Set("info_hash", string(ar.InfoHash[:]))
70         q.Set("peer_id", string(ar.PeerId[:]))
71         // AFAICT, port is mandatory, and there's no implied port key.
72         q.Set("port", fmt.Sprintf("%d", ar.Port))
73         q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
74         q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
75         q.Set("left", strconv.FormatUint(ar.Left, 10))
76         if ar.Event != None {
77                 q.Set("event", ar.Event.String())
78         }
79         // http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol
80         q.Set("compact", "1")
81         // According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO:
82         // Take EncryptionPolicy or something like it as a parameter.
83         q.Set("supportcrypto", "1")
84         if opts.ClientIp4.IP != nil {
85                 q.Set("ipv4", opts.ClientIp4.String())
86         }
87         if opts.ClientIp6.IP != nil {
88                 q.Set("ipv6", opts.ClientIp6.String())
89         }
90         _url.RawQuery = q.Encode()
91 }
92
93 func announceHTTP(opt Announce, _url *url.URL) (ret AnnounceResponse, err error) {
94         _url = httptoo.CopyURL(_url)
95         setAnnounceParams(_url, &opt.Request, opt)
96         req, err := http.NewRequest("GET", _url.String(), nil)
97         req.Header.Set("User-Agent", opt.UserAgent)
98         req.Host = opt.HostHeader
99         resp, err := opt.HttpClient.Do(req)
100         if err != nil {
101                 return
102         }
103         defer resp.Body.Close()
104         var buf bytes.Buffer
105         io.Copy(&buf, resp.Body)
106         if resp.StatusCode != 200 {
107                 err = fmt.Errorf("response from tracker: %s: %s", resp.Status, buf.String())
108                 return
109         }
110         var trackerResponse httpResponse
111         err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
112         if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
113                 err = nil
114         } else if err != nil {
115                 err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
116                 return
117         }
118         if trackerResponse.FailureReason != "" {
119                 err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason)
120                 return
121         }
122         vars.Add("successful http announces", 1)
123         ret.Interval = trackerResponse.Interval
124         ret.Leechers = trackerResponse.Incomplete
125         ret.Seeders = trackerResponse.Complete
126         if len(trackerResponse.Peers) != 0 {
127                 vars.Add("http responses with nonempty peers key", 1)
128         }
129         ret.Peers = trackerResponse.Peers
130         if len(trackerResponse.Peers6) != 0 {
131                 vars.Add("http responses with nonempty peers6 key", 1)
132         }
133         for _, na := range trackerResponse.Peers6 {
134                 ret.Peers = append(ret.Peers, Peer{
135                         IP:   na.IP,
136                         Port: na.Port,
137                 })
138         }
139         return
140 }