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