]> Sergey Matveev's repositories - btrtrc.git/blob - webseed/request.go
Drop support for go 1.20
[btrtrc.git] / webseed / request.go
1 package webseed
2
3 import (
4         "fmt"
5         "net/http"
6         "net/url"
7         "strings"
8
9         "github.com/anacrolix/torrent/metainfo"
10 )
11
12 type PathEscaper func(pathComps []string) string
13
14 // Escapes path name components suitable for appending to a webseed URL. This works for converting
15 // S3 object keys to URLs too.
16 //
17 // Contrary to the name, this actually does a QueryEscape, rather than a PathEscape. This works
18 // better with most S3 providers.
19 func EscapePath(pathComps []string) string {
20         return defaultPathEscaper(pathComps)
21 }
22
23 func defaultPathEscaper(pathComps []string) string {
24         var ret []string
25         for _, comp := range pathComps {
26                 esc := url.PathEscape(comp)
27                 // S3 incorrectly escapes + in paths to spaces, so we add an extra encoding for that. This
28                 // seems to be handled correctly regardless of whether an endpoint uses query or path
29                 // escaping.
30                 esc = strings.ReplaceAll(esc, "+", "%2B")
31                 ret = append(ret, esc)
32         }
33         return strings.Join(ret, "/")
34 }
35
36 func trailingPath(
37         infoName string,
38         fileComps []string,
39         pathEscaper PathEscaper,
40 ) string {
41         if pathEscaper == nil {
42                 pathEscaper = defaultPathEscaper
43         }
44         return pathEscaper(append([]string{infoName}, fileComps...))
45 }
46
47 // Creates a request per BEP 19.
48 func newRequest(
49         url_ string, fileIndex int,
50         info *metainfo.Info,
51         offset, length int64,
52         pathEscaper PathEscaper,
53 ) (*http.Request, error) {
54         fileInfo := info.UpvertedFiles()[fileIndex]
55         if strings.HasSuffix(url_, "/") {
56                 // BEP specifies that we append the file path. We need to escape each component of the path
57                 // for things like spaces and '#'.
58                 url_ += trailingPath(info.Name, fileInfo.Path, pathEscaper)
59         }
60         req, err := http.NewRequest(http.MethodGet, url_, nil)
61         if err != nil {
62                 return nil, err
63         }
64         if offset != 0 || length != fileInfo.Length {
65                 req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
66         }
67         return req, nil
68 }