- name: Set up Go
uses: actions/setup-go@v2
with:
- go-version: 1.16
+ go-version: 1.17
- name: Download Go modules
run: go mod download
)
tor.setChunkSize(2)
tor.cl.lock()
- err := tor.setInfoBytes(mi.InfoBytes)
+ err := tor.setInfoBytesLocked(mi.InfoBytes)
tor.cl.unlock()
require.NoError(t, err)
require.Len(t, tor.pieces, 3)
var (
builtinAnnounceList = [][]string{
- {"udp://tracker.openbittorrent.com:80"},
- {"udp://tracker.publicbt.com:80"},
- {"udp://tracker.istole.it:6969"},
+ {"http://p4p.arenabg.com:1337/announce"},
+ {"udp://tracker.opentrackr.org:1337/announce"},
+ {"udp://tracker.openbittorrent.com:6969/announce"},
}
)
return xerrors.New("y u no complete torrents?!")
}
if flags.Seed {
- outputStats(client)
- <-stop.C()
+ if len(client.Torrents()) == 0 {
+ log.Print("no torrents to seed")
+ } else {
+ outputStats(client)
+ <-stop.C()
+ }
}
return nil
}
github.com/edsrzf/mmap-go v1.0.0
github.com/elliotchance/orderedmap v1.4.0
github.com/frankban/quicktest v1.13.1
- github.com/fsnotify/fsnotify v1.4.9
+ github.com/fsnotify/fsnotify v1.5.1
github.com/google/btree v1.0.1
github.com/gorilla/websocket v1.4.2
- github.com/jessevdk/go-flags v1.4.0
+ github.com/jessevdk/go-flags v1.5.0
github.com/pion/datachannel v1.4.21
github.com/pion/ice/v2 v2.1.12 // indirect
github.com/pion/interceptor v0.0.15 // indirect
- github.com/pion/rtp v1.7.1 // indirect
+ github.com/pion/rtp v1.7.2 // indirect
github.com/pion/srtp/v2 v2.0.5 // indirect
github.com/pion/webrtc/v3 v3.0.32
github.com/pkg/errors v0.9.1
)
require (
- github.com/alexflint/go-scalar v1.0.0 // indirect
+ github.com/alexflint/go-scalar v1.1.0 // indirect
github.com/anacrolix/mmsg v1.0.0 // indirect
- github.com/anacrolix/stm v0.3.0-alpha // indirect
+ github.com/anacrolix/stm v0.3.0 // indirect
github.com/benbjohnson/immutable v0.3.0 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/pion/turn/v2 v2.0.5 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/rogpeppe/go-internal v1.6.1 // indirect
+ github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
go 1.17
-exclude bazil.org/fuse v0.0.0-20200419173433-3ba628eaf417
+exclude (
+ bazil.org/fuse v0.0.0-20200419173433-3ba628eaf417
+ bazil.org/fuse v0.0.0-20200524192727-fb710f7dfd05
+)
exclude github.com/willf/bitset v1.2.0
github.com/alexflint/go-arg v1.3.0/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0=
github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
-github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
+github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
+github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/anacrolix/chansync v0.0.0-20210524073341-a336ebc2de92/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
github.com/anacrolix/chansync v0.1.0 h1:4cIfJmEV8sYkSEMW2AXnPjX6iQT4plqELJ65pLna6OA=
github.com/anacrolix/chansync v0.1.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
github.com/anacrolix/stm v0.2.1-0.20201002073511-c35a2c748c6a/go.mod h1:spImf/rXwiAUoYYJK1YCZeWkpaHZ3kzjGFjwK5OStfU=
github.com/anacrolix/stm v0.2.1-0.20210310231625-45c211559de6/go.mod h1:spImf/rXwiAUoYYJK1YCZeWkpaHZ3kzjGFjwK5OStfU=
-github.com/anacrolix/stm v0.3.0-alpha h1:yhOHk1NPkpGKqCAOB4XkgFXwB5Eh2KU/WVMksNnxr2Y=
github.com/anacrolix/stm v0.3.0-alpha/go.mod h1:spImf/rXwiAUoYYJK1YCZeWkpaHZ3kzjGFjwK5OStfU=
+github.com/anacrolix/stm v0.3.0 h1:peQncJSNJtk1YBrFbW0DLKYqll+sa0kOk8EvXRcO+wA=
+github.com/anacrolix/stm v0.3.0/go.mod h1:spImf/rXwiAUoYYJK1YCZeWkpaHZ3kzjGFjwK5OStfU=
github.com/anacrolix/sync v0.0.0-20171108081538-eee974e4f8c1/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w=
github.com/anacrolix/sync v0.0.0-20180611022320-3c4cb11f5a01/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w=
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8=
github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
-github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
-github.com/pion/rtp v1.7.1 h1:hCaxfVgPGt13eF/Tu9RhVn04c+dAcRZmhdDWqUE13oY=
-github.com/pion/rtp v1.7.1/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
+github.com/pion/rtp v1.7.2 h1:HCDKDCixh7PVjkQTsqHAbk1lg+bx059EHxcnyl42dYs=
+github.com/pion/rtp v1.7.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
github.com/pion/webrtc/v3 v3.0.27/go.mod h1:QpLDmsU5a/a05n230gRtxZRvfHhFzn9ukGUL2x4G5ic=
github.com/pion/webrtc/v3 v3.0.32 h1:5J+zNep9am8Swh6kEMp+LaGXNvn6qQWpGkLBnVW44L4=
github.com/pion/webrtc/v3 v3.0.32/go.mod h1:wX3V5dQQUGCifhT1mYftC2kCrDQX6ZJ3B7Yad0R9JK0=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/dnscache v0.0.0-20190621150935-06bb5526f76b/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 h1:IJ6Df0uxPDtNoByV0KkzVKNseWvZFCNM/S9UoyOMCSI=
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
Files []FileInfo `bencode:"files,omitempty"` // BEP3, mutually exclusive with Length
}
-// This is a helper that sets Files and Pieces from a root path and its
-// children.
+// The Info.Name field is "advisory". For multi-file torrents it's usually a suggested directory
+// name. There are situations where we don't want a directory (like using the contents of a torrent
+// as the immediate contents of a directory), or the name is invalid. Transmission will inject the
+// name of the torrent file if it doesn't like the name, resulting in a different infohash
+// (https://github.com/transmission/transmission/issues/1775). To work around these situations, we
+// will use a sentinel name for compatibility with Transmission and to signal to our own client that
+// we intended to have no directory name. By exposing it in the API we can check for references to
+// this behaviour within this implementation.
+const NoName = "-"
+
+// This is a helper that sets Files and Pieces from a root path and its children.
func (info *Info) BuildFromFilePath(root string) (err error) {
- info.Name = filepath.Base(root)
+ info.Name = func() string {
+ b := filepath.Base(root)
+ switch b {
+ case ".", "..", string(filepath.Separator):
+ return NoName
+ default:
+ return b
+ }
+ }()
info.Files = nil
err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil {
--- /dev/null
+package storage
+
+import (
+ "github.com/anacrolix/torrent/metainfo"
+)
+
+func NewFileWithCompletion(baseDir string, completion PieceCompletion) ClientImplCloser {
+ return NewFileWithCustomPathMakerAndCompletion(baseDir, nil, completion)
+}
+
+// File storage with data partitioned by infohash.
+func NewFileByInfoHash(baseDir string) ClientImplCloser {
+ return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
+}
+
+// Deprecated: Allows passing a function to determine the path for storing torrent data. The
+// function is responsible for sanitizing the info if it uses some part of it (for example
+// sanitizing info.Name).
+func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImplCloser {
+ return NewFileWithCustomPathMakerAndCompletion(baseDir, pathMaker, pieceCompletionForDir(baseDir))
+}
+
+// Deprecated: Allows passing custom PieceCompletion
+func NewFileWithCustomPathMakerAndCompletion(
+ baseDir string,
+ pathMaker TorrentDirFilePathMaker,
+ completion PieceCompletion,
+) ClientImplCloser {
+ return NewFileOpts(NewFileClientOpts{
+ ClientBaseDir: baseDir,
+ TorrentDirMaker: pathMaker,
+ PieceCompletion: completion,
+ })
+}
--- /dev/null
+package storage
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/anacrolix/torrent/metainfo"
+)
+
+// Determines the filepath to be used for each file in a torrent.
+type FilePathMaker func(opts FilePathMakerOpts) string
+
+// Determines the directory for a given torrent within a storage client.
+type TorrentDirFilePathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string
+
+// Info passed to a FilePathMaker.
+type FilePathMakerOpts struct {
+ Info *metainfo.Info
+ File *metainfo.FileInfo
+}
+
+// defaultPathMaker just returns the storage client's base directory.
+func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
+ return baseDir
+}
+
+func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
+ return filepath.Join(baseDir, infoHash.HexString())
+}
+
+func isSubFilepath(base, sub string) bool {
+ rel, err := filepath.Rel(base, sub)
+ if err != nil {
+ return false
+ }
+ return rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator))
+}
"github.com/anacrolix/torrent/metainfo"
)
-// File-based storage for torrents, that isn't yet bound to a particular
-// torrent.
+// File-based storage for torrents, that isn't yet bound to a particular torrent.
type fileClientImpl struct {
- baseDir string
- pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string
- pc PieceCompletion
+ opts NewFileClientOpts
}
-// The Default path maker just returns the current path
-func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
- return baseDir
-}
-
-func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
- return filepath.Join(baseDir, infoHash.HexString())
-}
-
-// All Torrent data stored in this baseDir
+// All Torrent data stored in this baseDir. The info names of each torrent are used as directories.
func NewFile(baseDir string) ClientImplCloser {
return NewFileWithCompletion(baseDir, pieceCompletionForDir(baseDir))
}
-func NewFileWithCompletion(baseDir string, completion PieceCompletion) *fileClientImpl {
- return NewFileWithCustomPathMakerAndCompletion(baseDir, nil, completion)
-}
-
-// File storage with data partitioned by infohash.
-func NewFileByInfoHash(baseDir string) ClientImplCloser {
- return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
+type NewFileClientOpts struct {
+ // The base directory for all downloads.
+ ClientBaseDir string
+ FilePathMaker FilePathMaker
+ TorrentDirMaker TorrentDirFilePathMaker
+ PieceCompletion PieceCompletion
}
-// Allows passing a function to determine the path for storing torrent data. The function is
-// responsible for sanitizing the info if it uses some part of it (for example sanitizing
-// info.Name).
-func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImplCloser {
- return NewFileWithCustomPathMakerAndCompletion(baseDir, pathMaker, pieceCompletionForDir(baseDir))
-}
-
-// Allows passing custom PieceCompletion
-func NewFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) *fileClientImpl {
- if pathMaker == nil {
- pathMaker = defaultPathMaker
+// NewFileOpts creates a new ClientImplCloser that stores files using the OS native filesystem.
+func NewFileOpts(opts NewFileClientOpts) ClientImplCloser {
+ if opts.TorrentDirMaker == nil {
+ opts.TorrentDirMaker = defaultPathMaker
+ }
+ if opts.FilePathMaker == nil {
+ opts.FilePathMaker = func(opts FilePathMakerOpts) string {
+ var parts []string
+ if opts.Info.Name != metainfo.NoName {
+ parts = append(parts, opts.Info.Name)
+ }
+ return filepath.Join(append(parts, opts.File.Path...)...)
+ }
}
- return &fileClientImpl{
- baseDir: baseDir,
- pathMaker: pathMaker,
- pc: completion,
+ if opts.PieceCompletion == nil {
+ opts.PieceCompletion = pieceCompletionForDir(opts.ClientBaseDir)
}
+ return fileClientImpl{opts}
}
-func (me *fileClientImpl) Close() error {
- return me.pc.Close()
+func (me fileClientImpl) Close() error {
+ return me.opts.PieceCompletion.Close()
}
-func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (_ TorrentImpl, err error) {
- dir := fs.pathMaker(fs.baseDir, info, infoHash)
+func (fs fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (_ TorrentImpl, err error) {
+ dir := fs.opts.TorrentDirMaker(fs.opts.ClientBaseDir, info, infoHash)
upvertedFiles := info.UpvertedFiles()
files := make([]file, 0, len(upvertedFiles))
for i, fileInfo := range upvertedFiles {
- var s string
- s, err = ToSafeFilePath(append([]string{info.Name}, fileInfo.Path...)...)
- if err != nil {
- err = fmt.Errorf("file %v has unsafe path %q: %w", i, fileInfo.Path, err)
+ filePath := filepath.Join(dir, fs.opts.FilePathMaker(FilePathMakerOpts{
+ Info: info,
+ File: &fileInfo,
+ }))
+ if !isSubFilepath(dir, filePath) {
+ err = fmt.Errorf("file %v: path %q is not sub path of %q", i, filePath, dir)
return
}
f := file{
- path: filepath.Join(dir, s),
+ path: filePath,
length: fileInfo.Length,
}
if f.length == 0 {
files,
segments.NewIndex(common.LengthIterFromUpvertedFiles(upvertedFiles)),
infoHash,
- fs.pc,
+ fs.opts.PieceCompletion,
}
return TorrentImpl{
Piece: t.Piece,
)
type mapPieceCompletion struct {
+ // TODO: Generics
m sync.Map
}
pc PieceCompletion
}
+// TODO: Support all the same native filepath configuration that NewFileOpts provides.
func NewMMap(baseDir string) ClientImplCloser {
return NewMMapWithCompletion(baseDir, pieceCompletionForDir(baseDir))
}
package storage
import (
+ "fmt"
"log"
"path/filepath"
"testing"
+
+ "github.com/anacrolix/torrent/metainfo"
+ qt "github.com/frankban/quicktest"
)
func init() {
log.SetFlags(log.Flags() | log.Lshortfile)
}
-func TestSafePath(t *testing.T) {
- for _, _case := range []struct {
- input []string
- expected string
- expectErr bool
- }{
- {input: []string{"a", filepath.FromSlash(`b/../../..`)}, expectErr: true},
- {input: []string{"a", filepath.FromSlash(`b/../.././..`)}, expectErr: true},
- {input: []string{
- filepath.FromSlash(`NewSuperHeroMovie-2019-English-720p.avi /../../../../../Roaming/Microsoft/Windows/Start Menu/Programs/Startup/test3.exe`)},
- expectErr: true,
- },
- } {
+// I think these are mainly tests for bad metainfos that try to escape the client base directory.
+var safeFilePathTests = []struct {
+ input []string
+ expectErr bool
+}{
+ // We might want a test for invalid chars inside components, or file maker opt funcs returning
+ // absolute paths (and thus presumably clobbering earlier "makers").
+ {input: []string{"a", filepath.FromSlash(`b/..`)}, expectErr: false},
+ {input: []string{"a", filepath.FromSlash(`b/../../..`)}, expectErr: true},
+ {input: []string{"a", filepath.FromSlash(`b/../.././..`)}, expectErr: true},
+ {input: []string{
+ filepath.FromSlash(`NewSuperHeroMovie-2019-English-720p.avi /../../../../../Roaming/Microsoft/Windows/Start Menu/Programs/Startup/test3.exe`)},
+ expectErr: true,
+ },
+}
+
+// Tests the ToSafeFilePath func.
+func TestToSafeFilePath(t *testing.T) {
+ for _, _case := range safeFilePathTests {
actual, err := ToSafeFilePath(_case.input...)
if _case.expectErr {
if err != nil {
}
}
}
+
+// Check that safe file path handling still exists for the newer file-opt-maker variants.
+func TestFileOptsSafeFilePathHandling(t *testing.T) {
+ c := qt.New(t)
+ for i, _case := range safeFilePathTests {
+ c.Run(fmt.Sprintf("Case%v", i), func(c *qt.C) {
+ info := metainfo.Info{
+ Files: []metainfo.FileInfo{
+ {Path: _case.input},
+ },
+ }
+ client := NewFileOpts(NewFileClientOpts{
+ ClientBaseDir: "somedir",
+ })
+ defer func() { c.Check(client.Close(), qt.IsNil) }()
+ torImpl, err := client.OpenTorrent(&info, metainfo.Hash{})
+ if _case.expectErr {
+ c.Check(err, qt.Not(qt.IsNil))
+ } else {
+ c.Check(torImpl.Close(), qt.IsNil)
+ }
+ })
+ }
+}
defer me.mu.Unlock()
if me.db != nil {
err = me.db.Close()
+ me.db = nil
}
return
}
type PieceStateRuns []PieceStateRun
-func (me PieceStateRuns) String() string {
- ss := make([]string, 0, len(me))
- for _, psr := range me {
- ss = append(ss, psr.String())
+func (me PieceStateRuns) String() (s string) {
+ if len(me) > 0 {
+ var sb strings.Builder
+ sb.WriteString(me[0].String())
+ for i := 1; i < len(me); i += 1 {
+ sb.WriteByte(' ')
+ sb.WriteString(me[i].String())
+ }
+ return sb.String()
}
- return strings.Join(ss, " ")
+ return
}
// Returns the state of pieces of the torrent. They are grouped into runs of same state. The sum of
// the state run-lengths is the number of pieces in the torrent.
-func (t *Torrent) PieceStateRuns() PieceStateRuns {
+func (t *Torrent) PieceStateRuns() (runs PieceStateRuns) {
t.cl.rLock()
- defer t.cl.rUnlock()
- return t.pieceStateRuns()
+ runs = t.pieceStateRuns()
+ t.cl.rUnlock()
+ return
}
func (t *Torrent) PieceState(piece pieceIndex) PieceState {
}
// Called when metadata for a torrent becomes available.
-func (t *Torrent) setInfoBytes(b []byte) error {
+func (t *Torrent) setInfoBytesLocked(b []byte) error {
if metainfo.HashBytes(b) != t.infoHash {
return errors.New("info bytes have wrong hash")
}
// Don't have enough metadata pieces.
return nil
}
- err := t.setInfoBytes(t.metadataBytes)
+ err := t.setInfoBytesLocked(t.metadataBytes)
if err != nil {
t.invalidateMetadata()
return fmt.Errorf("error setting info bytes: %s", err)
func (t *Torrent) SetInfoBytes(b []byte) (err error) {
t.cl.lock()
defer t.cl.unlock()
- return t.setInfoBytes(b)
+ return t.setInfoBytesLocked(b)
}
// Returns true if connection is removed from torrent.Conns.
return
}
-func (t *Torrent) announceToDht(s DhtServer) error {
+func (t *Torrent) timeboxedAnnounceToDht(s DhtServer) error {
_, stop, err := t.AnnounceToDht(s)
if err != nil {
return err
t.numDHTAnnounces++
cl.unlock()
defer cl.lock()
- err := t.announceToDht(s)
+ err := t.timeboxedAnnounceToDht(s)
if err != nil {
t.logger.WithDefaultLevel(log.Warning).Printf("error announcing %q to DHT: %s", t, err)
}
cl.initLogger()
tt := cl.newTorrent(mi.HashInfoBytes(), badStorage{})
tt.setChunkSize(2)
- require.NoError(t, tt.setInfoBytes(mi.InfoBytes))
+ require.NoError(t, tt.setInfoBytesLocked(mi.InfoBytes))
tt.cl.lock()
tt.pieces[1]._dirtyChunks.AddRange(0, 3)
require.True(t, tt.pieceAllDirty(1))