github.com/anacrolix/torrent@v1.61.0/webseed/request.go (about) 1 package webseed 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "net/url" 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 strings.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 urlForFileIndex( 50 url_ string, fileIndex int, 51 info *metainfo.Info, 52 pathEscaper PathEscaper, 53 ) string { 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.BestName(), fileInfo.BestPath(), pathEscaper) 59 } 60 return url_ 61 } 62 63 // Creates a request per BEP 19. 64 func newRequest( 65 ctx context.Context, 66 url_ string, fileIndex int, 67 info *metainfo.Info, 68 offset, length int64, 69 pathEscaper PathEscaper, 70 ) (*http.Request, error) { 71 fileInfo := info.UpvertedFiles()[fileIndex] 72 url_ = urlForFileIndex(url_, fileIndex, info, pathEscaper) 73 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url_, nil) 74 if err != nil { 75 return nil, err 76 } 77 // We avoid Range requests if we can. We check the Content-Length elsewhere so that early 78 // detection is not lost. TODO: Try disabling this for CloudFlare? 79 if offset != 0 || length != fileInfo.Length { 80 req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1)) 81 } 82 return req, nil 83 }