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  }