From 02cc723750e69cd4ce327ced62d595b6c13c1479 Mon Sep 17 00:00:00 2001 From: afjoseph <7126721+afjoseph@users.noreply.github.com> Date: Thu, 21 Apr 2022 16:21:29 +0200 Subject: [PATCH 1/3] [webseed] Add a custom URL encoder for webseeds --- client.go | 2 +- spec.go | 4 ++++ torrent.go | 20 +++++++++++++++++--- webseed/client.go | 11 ++++++++++- webseed/request.go | 36 ++++++++++++++++++++++++++++++++---- webseed/request_test.go | 8 ++++---- 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index ee8261f3..7d763c13 100644 --- a/client.go +++ b/client.go @@ -1326,7 +1326,7 @@ func (t *Torrent) MergeSpec(spec *TorrentSpec) error { defer cl.unlock() t.initialPieceCheckDisabled = spec.DisableInitialPieceCheck for _, url := range spec.Webseeds { - t.addWebSeed(url) + t.addWebSeed(url, spec.EncodeWebSeedUrl) } for _, peerAddr := range spec.PeerAddrs { t.addPeer(PeerInfo{ diff --git a/spec.go b/spec.go index 332ea139..df5b06f9 100644 --- a/spec.go +++ b/spec.go @@ -36,6 +36,10 @@ type TorrentSpec struct { // Whether to allow data download or upload DisallowDataUpload bool DisallowDataDownload bool + + // Custom encoder for webseed URLs + // Leave nil to use the default (url.QueryEscape) + EncodeWebSeedUrl func(string) string } func TorrentSpecFromMagnetUri(uri string) (spec *TorrentSpec, err error) { diff --git a/torrent.go b/torrent.go index 6a75eff3..7810e9d1 100644 --- a/torrent.go +++ b/torrent.go @@ -2350,15 +2350,28 @@ func (t *Torrent) callbacks() *Callbacks { return &t.cl.config.Callbacks } -func (t *Torrent) AddWebSeeds(urls []string) { +type AddWebSeedsOptions struct { + // Custom encoder for webseed URLs + // Leave nil to use the default (url.QueryEscape) + EncodeUrl func(string) string +} + + +// Add web seeds to the torrent. +// If AddWebSeedsOptions is not nil, only the first one is used. +func (t *Torrent) AddWebSeeds(urls []string, opts ...AddWebSeedsOptions) { t.cl.lock() defer t.cl.unlock() for _, u := range urls { - t.addWebSeed(u) + if opts == nil { + t.addWebSeed(u, nil) + } else { + t.addWebSeed(u, opts[0].EncodeUrl) + } } } -func (t *Torrent) addWebSeed(url string) { +func (t *Torrent) addWebSeed(url string, encodeUrl func(string) string) { if t.cl.config.DisableWebseeds { return } @@ -2395,6 +2408,7 @@ func (t *Torrent) addWebSeed(url string) { r: r, } }, + EncodeUrl: encodeUrl, }, activeRequests: make(map[Request]webseed.Request, maxRequests), maxRequests: maxRequests, diff --git a/webseed/client.go b/webseed/client.go index a04b3430..ce8e3a42 100644 --- a/webseed/client.go +++ b/webseed/client.go @@ -41,6 +41,11 @@ func (r Request) Cancel() { r.cancel() } +type Spec struct { + Urls []string + EncodeUrl func(string) string +} + type Client struct { HttpClient *http.Client Url string @@ -52,6 +57,7 @@ type Client struct { // private in the future, if Client ever starts removing pieces. Pieces roaring.Bitmap ResponseBodyWrapper ResponseBodyWrapper + EncodeUrl func(string) string } type ResponseBodyWrapper func(io.Reader) io.Reader @@ -76,7 +82,10 @@ func (ws *Client) NewRequest(r RequestSpec) Request { ctx, cancel := context.WithCancel(context.Background()) var requestParts []requestPart if !ws.fileIndex.Locate(r, func(i int, e segments.Extent) bool { - req, err := NewRequest(ws.Url, i, ws.info, e.Start, e.Length) + req, err := NewRequestWithCustomUrlEncoding( + ws.Url, i, ws.info, e.Start, e.Length, + ws.EncodeUrl, + ) if err != nil { panic(err) } diff --git a/webseed/request.go b/webseed/request.go index 4e3ef609..3d4b4931 100644 --- a/webseed/request.go +++ b/webseed/request.go @@ -12,28 +12,56 @@ import ( // Escapes path name components suitable for appending to a webseed URL. This works for converting // S3 object keys to URLs too. +// Contrary to the name, this actually does a QueryEscape, rather than a +// PathEscape. This works better with most S3 providers. You can use +// EscapePathWithCustomEncoding for a custom encoding. func EscapePath(pathComps []string) string { + return escapePath(pathComps, nil) +} + +func escapePath(pathComps []string, encodeUrl func(string) string) string { + if encodeUrl == nil { + encodeUrl = url.QueryEscape + } return path.Join( func() (ret []string) { for _, comp := range pathComps { - ret = append(ret, url.QueryEscape(comp)) + ret = append(ret, encodeUrl(comp)) } return }()..., ) } -func trailingPath(infoName string, fileComps []string) string { - return EscapePath(append([]string{infoName}, fileComps...)) +func trailingPath(infoName string, fileComps []string, encodeUrl func(string) string) string { + return escapePath(append([]string{infoName}, fileComps...), encodeUrl) } // Creates a request per BEP 19. func NewRequest(url_ string, fileIndex int, info *metainfo.Info, offset, length int64) (*http.Request, error) { + return newRequest(url_, fileIndex, info, offset, length, nil) +} + +func NewRequestWithCustomUrlEncoding( + url_ string, fileIndex int, + info *metainfo.Info, + offset, length int64, + encodeUrl func(string) string, +) (*http.Request, error) { + return newRequest(url_, fileIndex, info, offset, length, encodeUrl) +} + +func newRequest( + url_ string, fileIndex int, + info *metainfo.Info, + offset, length int64, + encodeUrl func(string) string, +) (*http.Request, error) { fileInfo := info.UpvertedFiles()[fileIndex] if strings.HasSuffix(url_, "/") { // BEP specifies that we append the file path. We need to escape each component of the path // for things like spaces and '#'. - url_ += trailingPath(info.Name, fileInfo.Path) + url_ += trailingPath(info.Name, fileInfo.Path, encodeUrl) } req, err := http.NewRequest(http.MethodGet, url_, nil) if err != nil { diff --git a/webseed/request_test.go b/webseed/request_test.go index f7c18a03..b59e7784 100644 --- a/webseed/request_test.go +++ b/webseed/request_test.go @@ -10,7 +10,7 @@ import ( func TestTrailingPath(t *testing.T) { c := qt.New(t) test := func(parts []string, result string) { - unescaped, err := url.QueryUnescape(trailingPath(parts[0], parts[1:])) + unescaped, err := url.QueryUnescape(trailingPath(parts[0], parts[1:], url.QueryEscape)) if !c.Check(err, qt.IsNil) { return } @@ -23,7 +23,7 @@ func TestTrailingPath(t *testing.T) { } func TestTrailingPathForEmptyInfoName(t *testing.T) { - qt.Check(t, trailingPath("", []string{`ノ┬─┬ノ ︵ ( \o°o)\`}), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") - qt.Check(t, trailingPath("", []string{"hello", "world"}), qt.Equals, "hello/world") - qt.Check(t, trailingPath("war", []string{"and", "peace"}), qt.Equals, "war/and/peace") + qt.Check(t, trailingPath("", []string{`ノ┬─┬ノ ︵ ( \o°o)\`}, url.QueryEscape), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") + qt.Check(t, trailingPath("", []string{"hello", "world"}, url.QueryEscape), qt.Equals, "hello/world") + qt.Check(t, trailingPath("war", []string{"and", "peace"}, url.QueryEscape), qt.Equals, "war/and/peace") } From a79c3bd5d30d99fb33ab7c5334c7d017884d4403 Mon Sep 17 00:00:00 2001 From: afjoseph <7126721+afjoseph@users.noreply.github.com> Date: Fri, 22 Apr 2022 04:23:43 +0200 Subject: [PATCH 2/3] fixup! [webseed] Add a custom URL encoder for webseeds --- client.go | 2 +- spec.go | 3 +- torrent.go | 18 ++++++------ webseed/client.go | 6 ++-- webseed/request.go | 54 ++++++++++++++++++++++-------------- webseed/request_test.go | 61 +++++++++++++++++++++++++++++++++++------ 6 files changed, 101 insertions(+), 43 deletions(-) diff --git a/client.go b/client.go index 7d763c13..df746ecd 100644 --- a/client.go +++ b/client.go @@ -1326,7 +1326,7 @@ func (t *Torrent) MergeSpec(spec *TorrentSpec) error { defer cl.unlock() t.initialPieceCheckDisabled = spec.DisableInitialPieceCheck for _, url := range spec.Webseeds { - t.addWebSeed(url, spec.EncodeWebSeedUrl) + t.addWebSeed(url, spec.DefaultWebseedEscapePath) } for _, peerAddr := range spec.PeerAddrs { t.addPeer(PeerInfo{ diff --git a/spec.go b/spec.go index df5b06f9..6d290e8d 100644 --- a/spec.go +++ b/spec.go @@ -6,6 +6,7 @@ import ( "github.com/anacrolix/torrent/metainfo" pp "github.com/anacrolix/torrent/peer_protocol" "github.com/anacrolix/torrent/storage" + "github.com/anacrolix/torrent/webseed" ) // Specifies a new torrent for adding to a client, or additions to an existing Torrent. There are @@ -39,7 +40,7 @@ type TorrentSpec struct { // Custom encoder for webseed URLs // Leave nil to use the default (url.QueryEscape) - EncodeWebSeedUrl func(string) string + DefaultWebseedEscapePath webseed.PathEscaper } func TorrentSpecFromMagnetUri(uri string) (spec *TorrentSpec, err error) { diff --git a/torrent.go b/torrent.go index 7810e9d1..a08da702 100644 --- a/torrent.go +++ b/torrent.go @@ -2350,28 +2350,30 @@ func (t *Torrent) callbacks() *Callbacks { return &t.cl.config.Callbacks } -type AddWebSeedsOptions struct { +type AddWebseedsOpt func() *AddWebseedOpts + +type AddWebseedOpts struct { // Custom encoder for webseed URLs // Leave nil to use the default (url.QueryEscape) - EncodeUrl func(string) string + PathEscaper webseed.PathEscaper } // Add web seeds to the torrent. -// If AddWebSeedsOptions is not nil, only the first one is used. -func (t *Torrent) AddWebSeeds(urls []string, opts ...AddWebSeedsOptions) { +// If opt is not nil, only the first one is used. +func (t *Torrent) AddWebSeeds(urls []string, opt ...AddWebseedsOpt) { t.cl.lock() defer t.cl.unlock() for _, u := range urls { - if opts == nil { + if opt == nil { t.addWebSeed(u, nil) } else { - t.addWebSeed(u, opts[0].EncodeUrl) + t.addWebSeed(u, opt[0]().PathEscaper) } } } -func (t *Torrent) addWebSeed(url string, encodeUrl func(string) string) { +func (t *Torrent) addWebSeed(url string, pathEscaper webseed.PathEscaper) { if t.cl.config.DisableWebseeds { return } @@ -2408,7 +2410,7 @@ func (t *Torrent) addWebSeed(url string, encodeUrl func(string) string) { r: r, } }, - EncodeUrl: encodeUrl, + PathEscaper: pathEscaper, }, activeRequests: make(map[Request]webseed.Request, maxRequests), maxRequests: maxRequests, diff --git a/webseed/client.go b/webseed/client.go index ce8e3a42..d218cf0e 100644 --- a/webseed/client.go +++ b/webseed/client.go @@ -57,7 +57,7 @@ type Client struct { // private in the future, if Client ever starts removing pieces. Pieces roaring.Bitmap ResponseBodyWrapper ResponseBodyWrapper - EncodeUrl func(string) string + PathEscaper PathEscaper } type ResponseBodyWrapper func(io.Reader) io.Reader @@ -82,9 +82,9 @@ func (ws *Client) NewRequest(r RequestSpec) Request { ctx, cancel := context.WithCancel(context.Background()) var requestParts []requestPart if !ws.fileIndex.Locate(r, func(i int, e segments.Extent) bool { - req, err := NewRequestWithCustomUrlEncoding( + req, err := NewRequestWithOpts( ws.Url, i, ws.info, e.Start, e.Length, - ws.EncodeUrl, + ws.PathEscaper, ) if err != nil { panic(err) diff --git a/webseed/request.go b/webseed/request.go index 3d4b4931..460448d0 100644 --- a/webseed/request.go +++ b/webseed/request.go @@ -10,58 +10,70 @@ import ( "github.com/anacrolix/torrent/metainfo" ) +type PathEscaper func(pathComps []string) string + // Escapes path name components suitable for appending to a webseed URL. This works for converting // S3 object keys to URLs too. +// // Contrary to the name, this actually does a QueryEscape, rather than a // PathEscape. This works better with most S3 providers. You can use -// EscapePathWithCustomEncoding for a custom encoding. +// EscapePathWithOpts for a custom encoding. func EscapePath(pathComps []string) string { return escapePath(pathComps, nil) } -func escapePath(pathComps []string, encodeUrl func(string) string) string { - if encodeUrl == nil { - encodeUrl = url.QueryEscape - } - return path.Join( - func() (ret []string) { - for _, comp := range pathComps { - ret = append(ret, encodeUrl(comp)) - } - return - }()..., - ) +func EscapePathWithCustomEscaper(pathComps []string, pathEscaper PathEscaper) string { + return escapePath(pathComps, pathEscaper) } -func trailingPath(infoName string, fileComps []string, encodeUrl func(string) string) string { - return escapePath(append([]string{infoName}, fileComps...), encodeUrl) +func escapePath(pathComps []string, pathEscaper PathEscaper) string { + if pathEscaper != nil { + return pathEscaper(pathComps) + } + + var ret []string + for _, comp := range pathComps { + ret = append(ret, url.QueryEscape(comp)) + } + return path.Join(ret...) +} + +func trailingPath( + infoName string, + fileComps []string, + pathEscaper PathEscaper, +) string { + return escapePath(append([]string{infoName}, fileComps...), pathEscaper) } // Creates a request per BEP 19. -func NewRequest(url_ string, fileIndex int, info *metainfo.Info, offset, length int64) (*http.Request, error) { +func NewRequest( + url_ string, + fileIndex int, info *metainfo.Info, + offset, length int64) (*http.Request, error) { return newRequest(url_, fileIndex, info, offset, length, nil) } -func NewRequestWithCustomUrlEncoding( +func NewRequestWithOpts( url_ string, fileIndex int, info *metainfo.Info, offset, length int64, - encodeUrl func(string) string, + pathEscaper PathEscaper, ) (*http.Request, error) { - return newRequest(url_, fileIndex, info, offset, length, encodeUrl) + return newRequest(url_, fileIndex, info, offset, length, pathEscaper) } func newRequest( url_ string, fileIndex int, info *metainfo.Info, offset, length int64, - encodeUrl func(string) string, + pathEscaper PathEscaper, ) (*http.Request, error) { fileInfo := info.UpvertedFiles()[fileIndex] if strings.HasSuffix(url_, "/") { // BEP specifies that we append the file path. We need to escape each component of the path // for things like spaces and '#'. - url_ += trailingPath(info.Name, fileInfo.Path, encodeUrl) + url_ += escapePath(append([]string{info.Name}, fileInfo.Path...), pathEscaper) } req, err := http.NewRequest(http.MethodGet, url_, nil) if err != nil { diff --git a/webseed/request_test.go b/webseed/request_test.go index b59e7784..d39e20e9 100644 --- a/webseed/request_test.go +++ b/webseed/request_test.go @@ -2,28 +2,71 @@ package webseed import ( "net/url" + "path" "testing" qt "github.com/frankban/quicktest" ) -func TestTrailingPath(t *testing.T) { +func TestEscapePath(t *testing.T) { c := qt.New(t) - test := func(parts []string, result string) { - unescaped, err := url.QueryUnescape(trailingPath(parts[0], parts[1:], url.QueryEscape)) + test := func( + parts []string, result string, + escaper PathEscaper, + unescaper func(string) (string, error), + ) { + unescaped, err := unescaper(escapePath(parts, escaper)) if !c.Check(err, qt.IsNil) { return } c.Check(unescaped, qt.Equals, result) } - test([]string{"a_b-c", "d + e.f"}, "a_b-c/d + e.f") - test([]string{"a_1-b_c2", "d 3. (e, f).g"}, + + // Test with nil escapers (always uses url.QueryEscape) + // ------ + test( + []string{"a_b-c", "d + e.f"}, + "a_b-c/d + e.f", + nil, + url.QueryUnescape, + ) + test( + []string{"a_1-b_c2", "d 3. (e, f).g"}, "a_1-b_c2/d 3. (e, f).g", + nil, + url.QueryUnescape, + ) + + // Test with custom escapers + // ------ + test( + []string{"a_b-c", "d + e.f"}, + "a_b-c/d + e.f", + func(s []string) string { + var ret []string + for _, comp := range s { + ret = append(ret, url.PathEscape(comp)) + } + return path.Join(ret...) + }, + url.PathUnescape, + ) + test( + []string{"a_1-b_c2", "d 3. (e, f).g"}, + "a_1-b_c2/d 3. (e, f).g", + func(s []string) string { + var ret []string + for _, comp := range s { + ret = append(ret, url.PathEscape(comp)) + } + return path.Join(ret...) + }, + url.PathUnescape, ) } -func TestTrailingPathForEmptyInfoName(t *testing.T) { - qt.Check(t, trailingPath("", []string{`ノ┬─┬ノ ︵ ( \o°o)\`}, url.QueryEscape), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") - qt.Check(t, trailingPath("", []string{"hello", "world"}, url.QueryEscape), qt.Equals, "hello/world") - qt.Check(t, trailingPath("war", []string{"and", "peace"}, url.QueryEscape), qt.Equals, "war/and/peace") +func TestEscapePathForEmptyInfoName(t *testing.T) { + qt.Check(t, escapePath([]string{`ノ┬─┬ノ ︵ ( \o°o)\`}, nil), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") + qt.Check(t, escapePath([]string{"hello", "world"}, nil), qt.Equals, "hello/world") + qt.Check(t, escapePath([]string{"war", "and", "peace"}, nil), qt.Equals, "war/and/peace") } From d2d8125eea118567dab268457eda8f774abf98b9 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Tue, 26 Apr 2022 10:46:01 +1000 Subject: [PATCH 3/3] WebSeed PathEscaper API tweaks --- client.go | 2 +- spec.go | 13 +++++-------- torrent.go | 38 +++++++++++++++++--------------------- webseed/client.go | 7 +------ webseed/request.go | 40 +++++++++------------------------------- webseed/request_test.go | 12 ++++++------ 6 files changed, 39 insertions(+), 73 deletions(-) diff --git a/client.go b/client.go index df746ecd..ee8261f3 100644 --- a/client.go +++ b/client.go @@ -1326,7 +1326,7 @@ func (t *Torrent) MergeSpec(spec *TorrentSpec) error { defer cl.unlock() t.initialPieceCheckDisabled = spec.DisableInitialPieceCheck for _, url := range spec.Webseeds { - t.addWebSeed(url, spec.DefaultWebseedEscapePath) + t.addWebSeed(url) } for _, peerAddr := range spec.PeerAddrs { t.addPeer(PeerInfo{ diff --git a/spec.go b/spec.go index 6d290e8d..8cce3cb3 100644 --- a/spec.go +++ b/spec.go @@ -6,7 +6,6 @@ import ( "github.com/anacrolix/torrent/metainfo" pp "github.com/anacrolix/torrent/peer_protocol" "github.com/anacrolix/torrent/storage" - "github.com/anacrolix/torrent/webseed" ) // Specifies a new torrent for adding to a client, or additions to an existing Torrent. There are @@ -20,9 +19,11 @@ type TorrentSpec struct { InfoBytes []byte // The name to use if the Name field from the Info isn't available. DisplayName string - Webseeds []string - DhtNodes []string - PeerAddrs []string + // WebSeed URLs. For additional options add the URLs separately with Torrent.AddWebSeeds + // instead. + Webseeds []string + DhtNodes []string + PeerAddrs []string // The combination of the "xs" and "as" fields in magnet links, for now. Sources []string @@ -37,10 +38,6 @@ type TorrentSpec struct { // Whether to allow data download or upload DisallowDataUpload bool DisallowDataDownload bool - - // Custom encoder for webseed URLs - // Leave nil to use the default (url.QueryEscape) - DefaultWebseedEscapePath webseed.PathEscaper } func TorrentSpecFromMagnetUri(uri string) (spec *TorrentSpec, err error) { diff --git a/torrent.go b/torrent.go index a08da702..4cde15ff 100644 --- a/torrent.go +++ b/torrent.go @@ -2350,30 +2350,24 @@ func (t *Torrent) callbacks() *Callbacks { return &t.cl.config.Callbacks } -type AddWebseedsOpt func() *AddWebseedOpts +type AddWebSeedsOpt func(*webseed.Client) -type AddWebseedOpts struct { - // Custom encoder for webseed URLs - // Leave nil to use the default (url.QueryEscape) - PathEscaper webseed.PathEscaper -} - - -// Add web seeds to the torrent. -// If opt is not nil, only the first one is used. -func (t *Torrent) AddWebSeeds(urls []string, opt ...AddWebseedsOpt) { - t.cl.lock() - defer t.cl.unlock() - for _, u := range urls { - if opt == nil { - t.addWebSeed(u, nil) - } else { - t.addWebSeed(u, opt[0]().PathEscaper) - } +// Sets the WebSeed trailing path escaper for a webseed.Client. +func WebSeedPathEscaper(custom webseed.PathEscaper) AddWebSeedsOpt { + return func(c *webseed.Client) { + c.PathEscaper = custom } } -func (t *Torrent) addWebSeed(url string, pathEscaper webseed.PathEscaper) { +func (t *Torrent) AddWebSeeds(urls []string, opts ...AddWebSeedsOpt) { + t.cl.lock() + defer t.cl.unlock() + for _, u := range urls { + t.addWebSeed(u, opts...) + } +} + +func (t *Torrent) addWebSeed(url string, opts ...AddWebSeedsOpt) { if t.cl.config.DisableWebseeds { return } @@ -2410,11 +2404,13 @@ func (t *Torrent) addWebSeed(url string, pathEscaper webseed.PathEscaper) { r: r, } }, - PathEscaper: pathEscaper, }, activeRequests: make(map[Request]webseed.Request, maxRequests), maxRequests: maxRequests, } + for _, opt := range opts { + opt(&ws.client) + } ws.peer.initUpdateRequestsTimer() ws.requesterCond.L = t.cl.locker() for i := 0; i < maxRequests; i += 1 { diff --git a/webseed/client.go b/webseed/client.go index d218cf0e..a86be17a 100644 --- a/webseed/client.go +++ b/webseed/client.go @@ -41,11 +41,6 @@ func (r Request) Cancel() { r.cancel() } -type Spec struct { - Urls []string - EncodeUrl func(string) string -} - type Client struct { HttpClient *http.Client Url string @@ -82,7 +77,7 @@ func (ws *Client) NewRequest(r RequestSpec) Request { ctx, cancel := context.WithCancel(context.Background()) var requestParts []requestPart if !ws.fileIndex.Locate(r, func(i int, e segments.Extent) bool { - req, err := NewRequestWithOpts( + req, err := newRequest( ws.Url, i, ws.info, e.Start, e.Length, ws.PathEscaper, ) diff --git a/webseed/request.go b/webseed/request.go index 460448d0..a38e6372 100644 --- a/webseed/request.go +++ b/webseed/request.go @@ -15,22 +15,13 @@ type PathEscaper func(pathComps []string) string // Escapes path name components suitable for appending to a webseed URL. This works for converting // S3 object keys to URLs too. // -// Contrary to the name, this actually does a QueryEscape, rather than a -// PathEscape. This works better with most S3 providers. You can use -// EscapePathWithOpts for a custom encoding. +// Contrary to the name, this actually does a QueryEscape, rather than a PathEscape. This works +// better with most S3 providers. func EscapePath(pathComps []string) string { - return escapePath(pathComps, nil) + return defaultPathEscaper(pathComps) } -func EscapePathWithCustomEscaper(pathComps []string, pathEscaper PathEscaper) string { - return escapePath(pathComps, pathEscaper) -} - -func escapePath(pathComps []string, pathEscaper PathEscaper) string { - if pathEscaper != nil { - return pathEscaper(pathComps) - } - +func defaultPathEscaper(pathComps []string) string { var ret []string for _, comp := range pathComps { ret = append(ret, url.QueryEscape(comp)) @@ -43,26 +34,13 @@ func trailingPath( fileComps []string, pathEscaper PathEscaper, ) string { - return escapePath(append([]string{infoName}, fileComps...), pathEscaper) + if pathEscaper == nil { + pathEscaper = defaultPathEscaper + } + return pathEscaper(append([]string{infoName}, fileComps...)) } // Creates a request per BEP 19. -func NewRequest( - url_ string, - fileIndex int, info *metainfo.Info, - offset, length int64) (*http.Request, error) { - return newRequest(url_, fileIndex, info, offset, length, nil) -} - -func NewRequestWithOpts( - url_ string, fileIndex int, - info *metainfo.Info, - offset, length int64, - pathEscaper PathEscaper, -) (*http.Request, error) { - return newRequest(url_, fileIndex, info, offset, length, pathEscaper) -} - func newRequest( url_ string, fileIndex int, info *metainfo.Info, @@ -73,7 +51,7 @@ func newRequest( if strings.HasSuffix(url_, "/") { // BEP specifies that we append the file path. We need to escape each component of the path // for things like spaces and '#'. - url_ += escapePath(append([]string{info.Name}, fileInfo.Path...), pathEscaper) + url_ += trailingPath(info.Name, fileInfo.Path, pathEscaper) } req, err := http.NewRequest(http.MethodGet, url_, nil) if err != nil { diff --git a/webseed/request_test.go b/webseed/request_test.go index d39e20e9..7f691e0a 100644 --- a/webseed/request_test.go +++ b/webseed/request_test.go @@ -15,7 +15,7 @@ func TestEscapePath(t *testing.T) { escaper PathEscaper, unescaper func(string) (string, error), ) { - unescaped, err := unescaper(escapePath(parts, escaper)) + unescaped, err := unescaper(escaper(parts)) if !c.Check(err, qt.IsNil) { return } @@ -27,13 +27,13 @@ func TestEscapePath(t *testing.T) { test( []string{"a_b-c", "d + e.f"}, "a_b-c/d + e.f", - nil, + defaultPathEscaper, url.QueryUnescape, ) test( []string{"a_1-b_c2", "d 3. (e, f).g"}, "a_1-b_c2/d 3. (e, f).g", - nil, + defaultPathEscaper, url.QueryUnescape, ) @@ -66,7 +66,7 @@ func TestEscapePath(t *testing.T) { } func TestEscapePathForEmptyInfoName(t *testing.T) { - qt.Check(t, escapePath([]string{`ノ┬─┬ノ ︵ ( \o°o)\`}, nil), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") - qt.Check(t, escapePath([]string{"hello", "world"}, nil), qt.Equals, "hello/world") - qt.Check(t, escapePath([]string{"war", "and", "peace"}, nil), qt.Equals, "war/and/peace") + qt.Check(t, defaultPathEscaper([]string{`ノ┬─┬ノ ︵ ( \o°o)\`}), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") + qt.Check(t, defaultPathEscaper([]string{"hello", "world"}), qt.Equals, "hello/world") + qt.Check(t, defaultPathEscaper([]string{"war", "and", "peace"}), qt.Equals, "war/and/peace") }