request.go 1.9 KB

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