Use goftp.io/server/v2 instead of goftp.io/server

This commit is contained in:
2020-12-06 21:42:50 +08:00
parent 38c8c2b477
commit 23092fc575
61 changed files with 1482 additions and 3081 deletions

4
go.mod
View File

@@ -3,9 +3,7 @@ module goftp.io/ftpd
go 1.12
require (
gitea.com/goftp/leveldb-auth v0.0.0-20190711092309-e8e3d5ad5ac8
gitea.com/goftp/leveldb-perm v0.0.0-20190711092750-00b79e6da99c
gitea.com/goftp/qiniu-driver v0.0.0-20191027083326-6e505f23c4f0
gitea.com/lunny/tango v0.6.1
gitea.com/tango/binding v0.0.0-20200204091933-f90d5bac28d2
gitea.com/tango/flash v0.0.0-20190606021323-2b17fd0aed7c
@@ -17,5 +15,5 @@ require (
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/syndtr/goleveldb v1.0.0
github.com/unknwon/goconfig v0.0.0-20190425194916-3dba17dd7b9e
goftp.io/server v0.3.5-0.20200428022247-5cd49dc54bdb
goftp.io/server/v2 v2.0.0
)

32
go.sum
View File

@@ -1,11 +1,5 @@
gitea.com/goftp/file-driver v0.0.0-20190712091345-f79c2ed973f8 h1:uCOouUDYTyK+Zz86dnkvDBUKnbqfqA/Fk/hxp5sXWv8=
gitea.com/goftp/file-driver v0.0.0-20190712091345-f79c2ed973f8/go.mod h1:ghdogu0Da3rwYCSJ20JPgTiMcDpzeRbzvuFIOOW3G7w=
gitea.com/goftp/leveldb-auth v0.0.0-20190711092309-e8e3d5ad5ac8 h1:dvw8cqH+NbkiR0ikE3NSZpRKyBL9fOPuG9h4lKKDJ30=
gitea.com/goftp/leveldb-auth v0.0.0-20190711092309-e8e3d5ad5ac8/go.mod h1:TdnYhl3DGy5KS7bkOmfQTmDpHi2jhNWbEWW6yvWe8V8=
gitea.com/goftp/leveldb-perm v0.0.0-20190711092750-00b79e6da99c h1:PZpmF+sly/Uasc6e+JsbuJb6lFOkv1HnXOKekPnNW14=
gitea.com/goftp/leveldb-perm v0.0.0-20190711092750-00b79e6da99c/go.mod h1:MRIEZqzha2uKixiEk+D1qxh6utiACnnYRC9yRAbD/Vw=
gitea.com/goftp/qiniu-driver v0.0.0-20191027083326-6e505f23c4f0 h1:5RrQe6xVdRnQtWy8+c/bSCrKi0mCLd3WkTOF7hdfU3k=
gitea.com/goftp/qiniu-driver v0.0.0-20191027083326-6e505f23c4f0/go.mod h1:ANpqO7R4FuMV14mOGVE/FlMr7wDDKkfKBfMKnBbeBWU=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
gitea.com/lunny/tango v0.6.0 h1:Z9wX9fq5e2y9Da+cmlFpE3aAo1+GfTuhYIPPYdCD6kM=
@@ -23,8 +17,6 @@ gitea.com/tango/session v0.0.0-20190606020146-89f560e05167 h1:NqgUqEIKZD7hm++1Y5
gitea.com/tango/session v0.0.0-20190606020146-89f560e05167/go.mod h1:00EhoJmAqWFaTCwFu5/6WAu7iNCIAj95UbDLG7UDdKU=
gitea.com/tango/xsrf v0.0.0-20190606015726-fb1b2fb84238 h1:ti3ZmMFweqSwRCKbCVzza7SmNg3csKf58W2QY3nG/Ms=
gitea.com/tango/xsrf v0.0.0-20190606015726-fb1b2fb84238/go.mod h1:TTynPmrkQOiNzSrbDsDEL273wUIvPC7d+sUweqVPOkI=
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755 h1:1B7wb36fHLSwZfHg6ngZhhtIEHQjiC5H4p7qQGBEffg=
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -34,10 +26,6 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-xweb/uuid v0.0.0-20140604020037-d7dce341f851 h1:D46USD6oGNWzoJ/h5CWaFq3ELLoLoJzllJ03Xh78VYg=
github.com/go-xweb/uuid v0.0.0-20140604020037-d7dce341f851/go.mod h1:OmDEC58ZYO1Esk+Uy32SB6LWof9lyROl7q76dBFOCWw=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
github.com/goftp/server v0.0.0-20190304020633-eabccc535b5a h1:XTJuuzIub3zu2FgPqdFM9XFYYisXWu2hN/rFwayAIcY=
github.com/goftp/server v0.0.0-20190304020633-eabccc535b5a/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
@@ -55,7 +43,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
github.com/lunny/tango v0.5.6/go.mod h1:qW1SakbmM67DdOHN6mipeYWhB1Uu6lYsgU3u6fQmu5o=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/minio/minio-go/v6 v6.0.46 h1:waExJtO53xrnsNX//7cSc1h3478wqTryDx4RVD7o26I=
@@ -73,12 +60,6 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgF
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qiniu/api.v6 v6.0.9+incompatible h1:mG/jDC2GD9u2DqP1yIbX+USd3S60bQYSRh6Su6EbnsU=
github.com/qiniu/api.v6 v6.0.9+incompatible/go.mod h1:iJeMuW0i5a4O1SFx2LYtxY+9hkTfkYQJL8xTaGYGDA4=
github.com/qiniu/bytes v0.0.0-20140728010635-4887e7b2bde3 h1:PXNXOJs716xnMtH6kMkPlQfSG+x8m2Q31uTN+dQF10c=
github.com/qiniu/bytes v0.0.0-20140728010635-4887e7b2bde3/go.mod h1:5KFTwj5mNES3FmpAF+DEDuVolB/OVAUj3oNqPLriYbo=
github.com/qiniu/rpc v0.0.0-20140728010754-30c22466d920 h1:G6C/49DiPwATK+4oBi6OCf14WzCwNMTC1s5Udov4dwQ=
github.com/qiniu/rpc v0.0.0-20140728010754-30c22466d920/go.mod h1:vUC++Z6RsGp85+Oyiu1l5+mpao6xy/Vi1J/G1fKiwDk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
@@ -97,21 +78,12 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tango-contrib/binding v0.0.0-20170526073042-f86db18c4a9e/go.mod h1:MNdSKQVAIj+pPJfwPoqBSnG775qIUlH+T60yFz7mcTE=
github.com/tango-contrib/flash v0.0.0-20170619055053-40456640d164/go.mod h1:fjy8sKkKvITeoogusMWTPg9pRd83vivbUUnMyE6nJfQ=
github.com/tango-contrib/renders v0.0.0-20170526074344-86dba79a0240/go.mod h1:4kuIhm8N9wR3cGUFszRJQAvBRUaA7mw001P9BHKh0/s=
github.com/tango-contrib/session v0.0.0-20170526074221-3115f8ddf72d/go.mod h1:nAEED8H84/GpN5Ewf/dDJkOM7S0XWyxYAdwlkMTYe2w=
github.com/tango-contrib/xsrf v0.0.0-20170526074244-3dbe17fdad36/go.mod h1:i/hS2Yy51czffHIDR+2DMtjCRKKTP8WuYa3XHsh98rQ=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/goconfig v0.0.0-20190425194916-3dba17dd7b9e h1:WIu9z0oMGXDY19FEawfE3gNMXbJ3n2KDRvTHBnkxjnM=
github.com/unknwon/goconfig v0.0.0-20190425194916-3dba17dd7b9e/go.mod h1:qu2ZQ/wcC/if2u32263HTVC39PeOQRSmidQk3DuDFQ8=
goftp.io/ftpd v0.0.0-20180821024231-89d1eae9c018/go.mod h1:411wH3gYllZ079MJ6lG5UI++ERPF+VklHvGlMux980A=
goftp.io/server v0.0.0-20190712054601-1149070ae46b/go.mod h1:xreggPYu7ZuNe9PfbxiQca7bYGwU44IvlCCg3KzWJtQ=
goftp.io/server v0.0.0-20190812034929-9b3874d17690 h1:vHUbHALX1kwsxK6gFQeY19+10zk/pSABN/0vumWLCxQ=
goftp.io/server v0.0.0-20190812034929-9b3874d17690/go.mod h1:99FISrRpwKfaL4Ey/dX8N48WToveng/s2OXR5sJ3cnc=
goftp.io/server v0.3.5-0.20200428022247-5cd49dc54bdb h1:KyExRJCQa5lv7e1djnrZHOahYVdEKo3VMhTlNVrfrJw=
goftp.io/server v0.3.5-0.20200428022247-5cd49dc54bdb/go.mod h1:hFZeR656ErRt3ojMKt7H10vQ5nuWV1e0YeUTeorlR6k=
goftp.io/server/v2 v2.0.0 h1:FF8JKXXKDxAeO1uXEZz7G+IZwCDhl19dpVIlDtp3QAg=
goftp.io/server/v2 v2.0.0/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=

45
main.go
View File

@@ -7,13 +7,14 @@ import (
"goftp.io/ftpd/web"
ldbauth "gitea.com/goftp/leveldb-auth"
ldbperm "gitea.com/goftp/leveldb-perm"
qiniudriver "gitea.com/goftp/qiniu-driver"
"github.com/lunny/log"
_ "github.com/shurcooL/vfsgen"
"github.com/syndtr/goleveldb/leveldb"
"goftp.io/server"
ldbauth "goftp.io/ftpd/modules/ldbauth"
"goftp.io/server/v2"
"goftp.io/server/v2/driver/file"
minio_driver "goftp.io/server/v2/driver/minio"
)
var (
@@ -47,7 +48,7 @@ func main() {
perm = server.NewSimplePerm("root", "root")
}
var factory server.DriverFactory
var driver server.Driver
switch driverType {
case "file":
_, err = os.Lstat(rootPath)
@@ -57,26 +58,30 @@ func main() {
fmt.Println(err)
return
}
factory = &server.FileDriverFactory{
RootPath: rootPath,
Perm: perm,
driver, err = file.NewDriver(rootPath)
if err != nil {
fmt.Println(err)
return
}
case "qiniu":
factory = qiniudriver.NewQiniuDriverFactory(
/*factory = qiniudriver.NewQiniuDriverFactory(
qiniu.AccessKey,
qiniu.SecretKey,
qiniu.Bucket,
)
)*/
case "minio":
factory = server.NewMinioDriverFactory(
driver, err = minio_driver.NewDriver(
minio.Endpoint,
minio.AccessKey,
minio.SecretKey,
"",
minio.Bucket,
minio.UseSSL,
perm,
)
if err != nil {
fmt.Println(err)
return
}
default:
fmt.Println("no driver type input")
return
@@ -86,17 +91,18 @@ func main() {
if webCfg.Enabled {
web.DB = auth
web.Perm = perm
web.Factory = factory
web.Driver = driver
go web.Web(webCfg.Listen, "static", "templates", admin, pass,
webCfg.TLS, webCfg.CertFile, webCfg.KeyFile)
}
opt := &server.ServerOpts{
Name: serv.Name,
Factory: factory,
Port: serv.Port,
Auth: auth,
opt := &server.Options{
Name: serv.Name,
Driver: driver,
Port: serv.Port,
Auth: auth,
Perm: perm,
}
opt.TLS = serv.TLS
@@ -105,7 +111,10 @@ func main() {
opt.ExplicitFTPS = opt.TLS
// start ftp server
ftpServer := server.NewServer(opt)
ftpServer, err := server.NewServer(opt)
if err != nil {
log.Fatal("Error creating server:", err)
}
log.Info("FTP Server", version)
err = ftpServer.ListenAndServe()
if err != nil {

View File

@@ -7,13 +7,14 @@ import (
"goftp.io/ftpd/web"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
"goftp.io/server/v2"
)
type LDBAuth struct {
DB *leveldb.DB
}
func (db *LDBAuth) CheckPasswd(user, pass string) (bool, error) {
func (db *LDBAuth) CheckPasswd(ctx *server.Context, user, pass string) (bool, error) {
p, err := db.GetUser(user)
if err != nil {
if err == leveldb.ErrNotFound {
@@ -135,4 +136,4 @@ func (db *LDBAuth) GroupUser(group string, users *[]string) error {
*users = append(*users, key[len(prefix):])
}
return nil
}
}

View File

@@ -1,20 +0,0 @@
module gitea.com/goftp/leveldb-auth
go 1.12
require (
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755 // indirect
github.com/go-xweb/uuid v0.0.0-20140604020037-d7dce341f851 // indirect
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 // indirect
github.com/goftp/server v0.0.0-20190304020633-eabccc535b5a // indirect
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf // indirect
github.com/lunny/tango v0.5.6 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/syndtr/goleveldb v1.0.0
github.com/tango-contrib/binding v0.0.0-20170526073042-f86db18c4a9e // indirect
github.com/tango-contrib/flash v0.0.0-20170619055053-40456640d164 // indirect
github.com/tango-contrib/renders v0.0.0-20170526074344-86dba79a0240 // indirect
github.com/tango-contrib/session v0.0.0-20170526074221-3115f8ddf72d // indirect
github.com/tango-contrib/xsrf v0.0.0-20170526074244-3dbe17fdad36 // indirect
goftp.io/ftpd v0.0.0-20180821024231-89d1eae9c018
)

View File

@@ -1,76 +0,0 @@
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755 h1:1B7wb36fHLSwZfHg6ngZhhtIEHQjiC5H4p7qQGBEffg=
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-xweb/uuid v0.0.0-20140604020037-d7dce341f851 h1:D46USD6oGNWzoJ/h5CWaFq3ELLoLoJzllJ03Xh78VYg=
github.com/go-xweb/uuid v0.0.0-20140604020037-d7dce341f851/go.mod h1:OmDEC58ZYO1Esk+Uy32SB6LWof9lyROl7q76dBFOCWw=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
github.com/goftp/server v0.0.0-20190304020633-eabccc535b5a h1:XTJuuzIub3zu2FgPqdFM9XFYYisXWu2hN/rFwayAIcY=
github.com/goftp/server v0.0.0-20190304020633-eabccc535b5a/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf h1:2IYBd5TD/maMqTU2YUzp2tJL4cNaOYQ9EBullN9t9pk=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/lunny/tango v0.5.6 h1:QeUe+2ksZ3LScC+SKhDbS1wbS/ctuyRnZ3fAsL10J4M=
github.com/lunny/tango v0.5.6/go.mod h1:qW1SakbmM67DdOHN6mipeYWhB1Uu6lYsgU3u6fQmu5o=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tango-contrib/binding v0.0.0-20170526073042-f86db18c4a9e h1:BkZTZKwMdODKtAwCrZCEqPH06G4yJXICuuwOH/WWgio=
github.com/tango-contrib/binding v0.0.0-20170526073042-f86db18c4a9e/go.mod h1:MNdSKQVAIj+pPJfwPoqBSnG775qIUlH+T60yFz7mcTE=
github.com/tango-contrib/flash v0.0.0-20170619055053-40456640d164 h1:mHwwqszZFhjR58HvykmZ27BAg2vwLmT5aEq4zrrV5mo=
github.com/tango-contrib/flash v0.0.0-20170619055053-40456640d164/go.mod h1:fjy8sKkKvITeoogusMWTPg9pRd83vivbUUnMyE6nJfQ=
github.com/tango-contrib/renders v0.0.0-20170526074344-86dba79a0240 h1:AKdgx/7/20N3enfUt0N9G7od0/UyDr6PjGRdlsvLAjE=
github.com/tango-contrib/renders v0.0.0-20170526074344-86dba79a0240/go.mod h1:4kuIhm8N9wR3cGUFszRJQAvBRUaA7mw001P9BHKh0/s=
github.com/tango-contrib/session v0.0.0-20170526074221-3115f8ddf72d h1:rxx/ty+Tk6oyGWV2x+CgARAWdv28H0+tBMF83MEX34c=
github.com/tango-contrib/session v0.0.0-20170526074221-3115f8ddf72d/go.mod h1:nAEED8H84/GpN5Ewf/dDJkOM7S0XWyxYAdwlkMTYe2w=
github.com/tango-contrib/xsrf v0.0.0-20170526074244-3dbe17fdad36 h1:ymjpu0DZHjza+7G64EZt1WR+x1qPHTi23Ph5343m0z0=
github.com/tango-contrib/xsrf v0.0.0-20170526074244-3dbe17fdad36/go.mod h1:i/hS2Yy51czffHIDR+2DMtjCRKKTP8WuYa3XHsh98rQ=
goftp.io/ftpd v0.0.0-20180821024231-89d1eae9c018 h1:8/II4yyuHnxQBG+d3IUTflQjrjM5eTDerVgS2VLs7h4=
goftp.io/ftpd v0.0.0-20180821024231-89d1eae9c018/go.mod h1:411wH3gYllZ079MJ6lG5UI++ERPF+VklHvGlMux980A=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,13 +0,0 @@
kind: pipeline
name: default
steps:
- name: test
image: golang:1.12
environment:
GO111MODULE: on
GOPROXY: https://goproxy.cn
commands:
- go build -v
- go vet ./...
- go test -v -race -coverprofile=coverage.txt -covermode=atomic

View File

@@ -1 +0,0 @@
.history

View File

@@ -1,3 +0,0 @@
# qiniu-driver
Servcie qiniu bucket as a ftp driver

View File

@@ -1,244 +0,0 @@
package qiniudriver
import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/qiniu/api.v6/auth/digest"
"github.com/qiniu/api.v6/conf"
qio "github.com/qiniu/api.v6/io"
"github.com/qiniu/api.v6/rs"
"github.com/qiniu/api.v6/rsf"
"goftp.io/server"
)
type QiniuDriver struct {
curDir string
client rs.Client
client2 rsf.Client
bucket string
}
func (driver *QiniuDriver) Init(conn *server.Conn) {
//driver.conn = conn
}
func (driver *QiniuDriver) ChangeDir(path string) error {
f, err := driver.Stat(path)
if err != nil {
return err
}
if !f.IsDir() {
return errors.New("not a dir")
}
driver.curDir = path
return nil
}
func (driver *QiniuDriver) Stat(key string) (server.FileInfo, error) {
if strings.HasSuffix(key, "/") {
return &FileInfo{key, true, rs.Entry{}}, nil
}
entry, err := driver.client.Stat(nil, driver.bucket, strings.TrimLeft(key, "/"))
if err != nil {
entries, _, _ := driver.client2.ListPrefix(nil, driver.bucket, strings.TrimLeft(key, "/")+"/", "", 1)
if len(entries) > 0 {
return &FileInfo{key, true, rs.Entry{}}, nil
}
return nil, errors.New("dir not exists")
}
return &FileInfo{key, false, entry}, nil
}
func (driver *QiniuDriver) ListDir(prefix string, callback func(server.FileInfo) error) error {
d := strings.TrimLeft(prefix, "/")
if d != "" {
d = d + "/"
}
entries, _, err := driver.client2.ListPrefix(nil, driver.bucket, d, "", 1000)
if err == io.EOF {
err = nil
}
if err != nil {
return err
}
dirCache := make(map[string]bool)
for _, entry := range entries {
if prefix != "/" && prefix != "" && !strings.HasPrefix(entry.Key, d) {
continue
}
key := strings.TrimLeft(strings.TrimLeft(entry.Key, d), "/")
if key == "" {
continue
}
var f server.FileInfo
if strings.Contains(key, "/") {
key := strings.Trim(strings.Split(key, "/")[0], "/")
if _, ok := dirCache[key]; ok {
continue
}
dirCache[key] = true
f = &FileInfo{name: key, isDir: true}
} else {
f = &FileInfo{
name: key,
Entry: rs.Entry{
Hash: entry.Hash,
Fsize: entry.Fsize,
PutTime: entry.PutTime,
MimeType: entry.MimeType,
Customer: "",
},
}
}
err = callback(f)
if err != nil {
return err
}
}
return nil
}
func (driver *QiniuDriver) DeleteDir(key string) error {
d := strings.TrimLeft(key, "/")
entries, _, err := driver.client2.ListPrefix(nil, driver.bucket, d, "", 1000)
if err == io.EOF {
err = nil
}
if err != nil {
return err
}
if len(entries) == 0 {
return nil
}
delentries := make([]rs.EntryPath, 0)
for _, entry := range entries {
delentries = append(delentries, rs.EntryPath{
Bucket: driver.bucket,
Key: entry.Key,
})
}
fmt.Println("delete entries:", delentries)
_, err = driver.client.BatchDelete(nil, delentries)
return err
}
func (driver *QiniuDriver) DeleteFile(key string) error {
fmt.Println("delete file", key)
return driver.client.Delete(nil, driver.bucket, strings.TrimLeft(key, "/"))
}
func (driver *QiniuDriver) Rename(keySrc, keyDest string) error {
fmt.Println("rename from", keySrc, keyDest)
var from = strings.TrimLeft(keySrc, "/")
var to = strings.TrimLeft(keyDest, "/")
info, err := driver.client.Stat(nil, driver.bucket, from)
if err != nil && strings.Contains(err.Error(), "no such file or directory") {
from = strings.TrimLeft(keySrc, "/") + "/"
to = strings.TrimLeft(keyDest, "/") + "/"
info, err = driver.client.Stat(nil, driver.bucket, from)
if err != nil {
return err
}
entries, _, err := driver.client2.ListPrefix(nil, driver.bucket, from, "", 1000)
if err != nil {
return err
}
for _, entry := range entries {
newKey := strings.Replace(entry.Key, from, to, 1)
err = driver.client.Move(nil, driver.bucket, entry.Key, driver.bucket, newKey)
if err != nil {
return err
}
}
return nil
}
if err != nil {
fmt.Println(err)
return err
}
fmt.Println(info, from, to)
return driver.client.Move(nil, driver.bucket, from, driver.bucket, to)
}
func (driver *QiniuDriver) MakeDir(path string) error {
dir := strings.TrimLeft(path, "/") + "/"
fmt.Println("mkdir", dir)
var s string
reader := strings.NewReader(s)
_, err := driver.PutFile(dir, reader, false)
return err
}
func (driver *QiniuDriver) GetFile(key string, offset int64) (int64, io.ReadCloser, error) {
stat, err := driver.Stat(key)
if err != nil {
return 0, nil, err
}
key = strings.TrimLeft(key, "/")
domain := fmt.Sprintf("%s.qiniudn.com", driver.bucket)
baseUrl := rs.MakeBaseUrl(domain, key)
policy := rs.GetPolicy{}
downUrl := policy.MakeRequest(baseUrl, nil)
resp, err := http.Get(downUrl)
if err != nil {
return 0, nil, err
}
return stat.Size(), NewSkipReadCloser(resp.Body, offset), nil
}
func (driver *QiniuDriver) PutFile(key string, data io.Reader, appendData bool) (int64, error) {
var err error
var ret qio.PutRet
var extra = &qio.PutExtra{}
putPolicy := rs.PutPolicy{
Scope: driver.bucket,
}
uptoken := putPolicy.Token(nil)
rd := CountReader(data)
err = qio.Put(nil, &ret, uptoken, strings.TrimLeft(key, "/"), rd, extra)
if err != nil {
return 0, err
}
return int64(rd.Size()), nil
}
type QiniuDriverFactory struct {
bucket string
}
func NewQiniuDriverFactory(accessKey, secretKey, bucket string) server.DriverFactory {
conf.ACCESS_KEY = accessKey
conf.SECRET_KEY = secretKey
return &QiniuDriverFactory{bucket}
}
func (factory *QiniuDriverFactory) NewDriver() (server.Driver, error) {
mac := &digest.Mac{
AccessKey: conf.ACCESS_KEY,
SecretKey: []byte(conf.SECRET_KEY),
}
client := rs.New(mac)
client2 := rsf.New(mac)
return &QiniuDriver{"/", client, client2, factory.bucket}, nil
}

View File

@@ -1,49 +0,0 @@
package qiniudriver
import (
"os"
"time"
"github.com/qiniu/api.v6/rs"
)
type FileInfo struct {
name string
isDir bool
rs.Entry
}
func (f *FileInfo) Name() string {
return f.name
}
func (f *FileInfo) Size() int64 {
return f.Entry.Fsize
}
func (f *FileInfo) Mode() os.FileMode {
if f.isDir {
return os.ModeDir | os.ModePerm
}
return os.ModePerm
}
func (f *FileInfo) ModTime() time.Time {
return time.Unix(0, f.Entry.PutTime*100)
}
func (f *FileInfo) IsDir() bool {
return f.isDir
}
func (f *FileInfo) Sys() interface{} {
return nil
}
func (f *FileInfo) Owner() string {
return "qiniu"
}
func (f *FileInfo) Group() string {
return "qiniu"
}

View File

@@ -1,10 +0,0 @@
module gitea.com/goftp/qiniu-driver
go 1.12
require (
github.com/qiniu/api.v6 v6.0.9+incompatible
github.com/qiniu/bytes v0.0.0-20140728010635-4887e7b2bde3 // indirect
github.com/qiniu/rpc v0.0.0-20140728010754-30c22466d920 // indirect
goftp.io/server v0.0.0-20190812034929-9b3874d17690
)

View File

@@ -1,22 +0,0 @@
gitea.com/goftp/file-driver v0.0.0-20190712091345-f79c2ed973f8 h1:uCOouUDYTyK+Zz86dnkvDBUKnbqfqA/Fk/hxp5sXWv8=
gitea.com/goftp/file-driver v0.0.0-20190712091345-f79c2ed973f8/go.mod h1:ghdogu0Da3rwYCSJ20JPgTiMcDpzeRbzvuFIOOW3G7w=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
github.com/goftp/server v0.0.0-20190304020633-eabccc535b5a/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf h1:2IYBd5TD/maMqTU2YUzp2tJL4cNaOYQ9EBullN9t9pk=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qiniu/api.v6 v6.0.9+incompatible h1:mG/jDC2GD9u2DqP1yIbX+USd3S60bQYSRh6Su6EbnsU=
github.com/qiniu/api.v6 v6.0.9+incompatible/go.mod h1:iJeMuW0i5a4O1SFx2LYtxY+9hkTfkYQJL8xTaGYGDA4=
github.com/qiniu/bytes v0.0.0-20140728010635-4887e7b2bde3 h1:PXNXOJs716xnMtH6kMkPlQfSG+x8m2Q31uTN+dQF10c=
github.com/qiniu/bytes v0.0.0-20140728010635-4887e7b2bde3/go.mod h1:5KFTwj5mNES3FmpAF+DEDuVolB/OVAUj3oNqPLriYbo=
github.com/qiniu/rpc v0.0.0-20140728010754-30c22466d920 h1:G6C/49DiPwATK+4oBi6OCf14WzCwNMTC1s5Udov4dwQ=
github.com/qiniu/rpc v0.0.0-20140728010754-30c22466d920/go.mod h1:vUC++Z6RsGp85+Oyiu1l5+mpao6xy/Vi1J/G1fKiwDk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
goftp.io/server v0.0.0-20190712054601-1149070ae46b/go.mod h1:xreggPYu7ZuNe9PfbxiQca7bYGwU44IvlCCg3KzWJtQ=
goftp.io/server v0.0.0-20190812034929-9b3874d17690 h1:vHUbHALX1kwsxK6gFQeY19+10zk/pSABN/0vumWLCxQ=
goftp.io/server v0.0.0-20190812034929-9b3874d17690/go.mod h1:99FISrRpwKfaL4Ey/dX8N48WToveng/s2OXR5sJ3cnc=

View File

@@ -1,23 +0,0 @@
package qiniudriver
import "io"
type countReader struct {
reader io.Reader
counts int
}
func (cr *countReader) Size() int {
return cr.counts
}
func (cr *countReader) Read(p []byte) (n int, err error) {
rs, err := cr.reader.Read(p)
cr.counts += rs
return rs, err
}
// CountReader returns a Reader that's is just for counting the total bytes of read.
func CountReader(reader io.Reader) *countReader {
return &countReader{reader, 0}
}

View File

@@ -1,32 +0,0 @@
package qiniudriver
import (
"io"
"io/ioutil"
)
func NewSkipReadCloser(rd io.ReadCloser, count int64) io.ReadCloser {
return &SkipReadCloser{
ReadCloser: rd,
count: count,
}
}
type SkipReadCloser struct {
io.ReadCloser
count int64
skipped bool
}
func (s *SkipReadCloser) Read(data []byte) (int, error) {
if !s.skipped {
if s.count > 0 {
_, err := io.CopyN(ioutil.Discard, s.ReadCloser, s.count)
if err != nil {
return 0, err
}
}
s.skipped = true
}
return s.ReadCloser.Read(data)
}

View File

@@ -1,144 +0,0 @@
package digest
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"io"
"net/http"
. "github.com/qiniu/api.v6/conf"
"github.com/qiniu/bytes/seekable"
)
// ----------------------------------------------------------
type Mac struct {
AccessKey string
SecretKey []byte
}
func (mac *Mac) Sign(data []byte) (token string) {
h := hmac.New(sha1.New, mac.SecretKey)
h.Write(data)
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
return mac.AccessKey + ":" + sign[:27]
}
func (mac *Mac) SignWithData(b []byte) (token string) {
blen := base64.URLEncoding.EncodedLen(len(b))
key := mac.AccessKey
nkey := len(key)
ret := make([]byte, nkey+30+blen)
base64.URLEncoding.Encode(ret[nkey+30:], b)
h := hmac.New(sha1.New, mac.SecretKey)
h.Write(ret[nkey+30:])
digest := h.Sum(nil)
copy(ret, key)
ret[nkey] = ':'
base64.URLEncoding.Encode(ret[nkey+1:], digest)
ret[nkey+29] = ':'
return string(ret)
}
func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err error) {
h := hmac.New(sha1.New, mac.SecretKey)
u := req.URL
data := u.Path
if u.RawQuery != "" {
data += "?" + u.RawQuery
}
io.WriteString(h, data+"\n")
if incbody {
s2, err2 := seekable.New(req)
if err2 != nil {
return "", err2
}
h.Write(s2.Bytes())
}
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
token = mac.AccessKey + ":" + sign
return
}
func Sign(mac *Mac, data []byte) string {
if mac == nil {
mac = &Mac{ACCESS_KEY, []byte(SECRET_KEY)}
}
return mac.Sign(data)
}
func SignWithData(mac *Mac, data []byte) string {
if mac == nil {
mac = &Mac{ACCESS_KEY, []byte(SECRET_KEY)}
}
return mac.SignWithData(data)
}
// ---------------------------------------------------------------------------------------
type Transport struct {
mac Mac
transport http.RoundTripper
}
func incBody(req *http.Request) bool {
if req.Body == nil {
return false
}
if ct, ok := req.Header["Content-Type"]; ok {
switch ct[0] {
case "application/x-www-form-urlencoded":
return true
}
}
return false
}
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
token, err := t.mac.SignRequest(req, incBody(req))
if err != nil {
return
}
req.Header.Set("Authorization", "QBox "+token)
return t.transport.RoundTrip(req)
}
func NewTransport(mac *Mac, transport http.RoundTripper) *Transport {
if transport == nil {
transport = http.DefaultTransport
}
t := &Transport{transport: transport}
if mac == nil {
t.mac.AccessKey = ACCESS_KEY
t.mac.SecretKey = []byte(SECRET_KEY)
} else {
t.mac = *mac
}
return t
}
func NewClient(mac *Mac, transport http.RoundTripper) *http.Client {
t := NewTransport(mac, transport)
return &http.Client{Transport: t}
}
// ---------------------------------------------------------------------------------------

View File

@@ -1,41 +0,0 @@
package conf
import (
"errors"
"fmt"
"regexp"
"runtime"
"github.com/qiniu/rpc"
)
var UP_HOST = "http://upload.qiniu.com"
var RS_HOST = "http://rs.qbox.me"
var RSF_HOST = "http://rsf.qbox.me"
var PUB_HOST = "http://pub.qbox.me"
var IO_HOST = "http://iovip.qbox.me"
var ACCESS_KEY string
var SECRET_KEY string
var version = "6.0.6"
var userPattern = regexp.MustCompile("^[a-zA-Z0-9_.-]*$")
// user should be [A-Za-z0-9]*
func SetUser(user string) error {
if !userPattern.MatchString(user) {
return errors.New("invalid user format")
}
rpc.UserAgent = formatUserAgent(user)
return nil
}
func formatUserAgent(user string) string {
return fmt.Sprintf("QiniuGo/%s (%s; %s; %s) %s", version, runtime.GOOS, runtime.GOARCH, user, runtime.Version())
}
func init() {
SetUser("")
}

View File

@@ -1,249 +0,0 @@
package io
import (
"bytes"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"mime/multipart"
"net/textproto"
"os"
"strconv"
"strings"
. "github.com/qiniu/api.v6/conf"
"github.com/qiniu/rpc"
)
// ----------------------------------------------------------
// @gist PutExtra
type PutExtra struct {
Params map[string]string //可选,用户自定义参数,必须以 "x:" 开头
//若不以x:开头,则忽略
MimeType string //可选,当为 "" 时候,服务端自动判断
Crc32 uint32
CheckCrc uint32
// CheckCrc == 0: 表示不进行 crc32 校验
// CheckCrc == 1: 对于 Put 等同于 CheckCrc = 2对于 PutFile 会自动计算 crc32 值
// CheckCrc == 2: 表示进行 crc32 校验,且 crc32 值就是上面的 Crc32 变量
}
// @endgist
type PutRet struct {
Hash string `json:"hash"` // 如果 uptoken 没有指定 ReturnBody那么返回值是标准的 PutRet 结构
Key string `json:"key"`
}
var tmpFilePrefix = "qiniu-go-sdk-tmpfile"
// ----------------------------------------------------------
// !!! Deprecated !!!
//
// 1. 不推荐使用该组 API, 因为可能造成本地磁盘IO
// 2. 如果只是纯粹上传一个 io.Reader, 请使用 Put2 或者 PutWithoutKey2
// 3. 如果需要上传一个文件, 请使用 PutFile 或者 PutFileWithoutKey
func Put(l rpc.Logger, ret interface{}, uptoken, key string, data io.Reader, extra *PutExtra) error {
return putReader(l, ret, uptoken, key, true, data, extra)
}
func PutWithoutKey(l rpc.Logger, ret interface{}, uptoken string, data io.Reader, extra *PutExtra) error {
return putReader(l, ret, uptoken, "", false, data, extra)
}
func putReader(l rpc.Logger, ret interface{}, uptoken, key string, hasKey bool, data io.Reader, extra *PutExtra) error {
rs, ok := data.(io.ReadSeeker)
if ok {
// 通过 Seeker 接口获取大小
size, err := rs.Seek(0, 2)
if err != nil {
return err
}
_, err = rs.Seek(0, 0)
if err != nil {
return err
}
return put(l, ret, uptoken, key, hasKey, data, size, extra)
} else {
// 写临时文件
tmpf, err := ioutil.TempFile(os.TempDir(), tmpFilePrefix)
if err != nil {
return err
}
fname := tmpf.Name()
defer os.Remove(fname)
_, err = io.Copy(tmpf, data)
if err != nil {
tmpf.Close()
return err
}
tmpf.Close()
return putFile(l, ret, uptoken, key, hasKey, fname, extra)
}
return nil
}
// ----------------------------------------------------------
func put(l rpc.Logger, ret interface{}, uptoken, key string, hasKey bool, data io.Reader, size int64, extra *PutExtra) error {
// CheckCrc == 1: 对于 Put 和 PutWithoutKey 等同于 CheckCrc == 2
if extra != nil {
if extra.CheckCrc == 1 {
extra1 := *extra
extra = &extra1
extra.CheckCrc = 2
}
}
return putWrite(l, ret, uptoken, key, hasKey, data, size, extra)
}
func Put2(l rpc.Logger, ret interface{}, uptoken, key string, data io.Reader, size int64, extra *PutExtra) error {
return put(l, ret, uptoken, key, true, data, size, extra)
}
func PutWithoutKey2(l rpc.Logger, ret interface{}, uptoken string, data io.Reader, size int64, extra *PutExtra) error {
return put(l, ret, uptoken, "", false, data, size, extra)
}
// ----------------------------------------------------------
func PutFile(l rpc.Logger, ret interface{}, uptoken, key, localFile string, extra *PutExtra) (err error) {
return putFile(l, ret, uptoken, key, true, localFile, extra)
}
func PutFileWithoutKey(l rpc.Logger, ret interface{}, uptoken, localFile string, extra *PutExtra) (err error) {
return putFile(l, ret, uptoken, "", false, localFile, extra)
}
func putFile(l rpc.Logger, ret interface{}, uptoken, key string, hasKey bool, localFile string, extra *PutExtra) (err error) {
f, err := os.Open(localFile)
if err != nil {
return
}
defer f.Close()
finfo, err := f.Stat()
if err != nil {
return
}
fsize := finfo.Size()
if extra != nil && extra.CheckCrc == 1 {
extra.Crc32, err = getFileCrc32(f)
if err != nil {
return
}
}
return putWrite(l, ret, uptoken, key, hasKey, f, fsize, extra)
}
// ----------------------------------------------------------
func putWrite(l rpc.Logger, ret interface{}, uptoken, key string, hasKey bool, data io.Reader, size int64, extra *PutExtra) error {
var b bytes.Buffer
writer := multipart.NewWriter(&b)
err := writeMultipart(writer, uptoken, key, hasKey, extra)
if err != nil {
return err
}
lastLine := fmt.Sprintf("\r\n--%s--\r\n", writer.Boundary())
r := bytes.NewReader([]byte(lastLine))
bodyLen := int64(b.Len()) + size + int64(len(lastLine))
mr := io.MultiReader(&b, data, r)
contentType := writer.FormDataContentType()
return rpc.DefaultClient.CallWith64(l, ret, UP_HOST, contentType, mr, bodyLen)
}
/*
* extra.CheckCrc:
* 0: 不进行crc32校验
* 1: 以writeMultipart自动生成crc32的值进行校验
* 2: 以extra.Crc32的值进行校验
* other: 和2一样 以 extra.Crc32的值进行校验
*/
func writeMultipart(writer *multipart.Writer, uptoken, key string, hasKey bool, extra *PutExtra) (err error) {
if extra == nil {
extra = &PutExtra{}
}
//token
if err = writer.WriteField("token", uptoken); err != nil {
return
}
//key
if hasKey {
if err = writer.WriteField("key", key); err != nil {
return
}
}
// extra.Params
if extra.Params != nil {
for k, v := range extra.Params {
err = writer.WriteField(k, v)
if err != nil {
return
}
}
}
//extra.CheckCrc
if extra.CheckCrc != 0 {
err = writer.WriteField("crc32", strconv.FormatInt(int64(extra.Crc32), 10))
if err != nil {
return
}
}
//file
head := make(textproto.MIMEHeader)
// default the filename is same as key , but ""
var fileName = key
if fileName == "" {
fileName = "filename"
}
head.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="file"; filename="%s"`, escapeQuotes(fileName)))
if extra.MimeType != "" {
head.Set("Content-Type", extra.MimeType)
}
_, err = writer.CreatePart(head)
return err
}
// ----------------------------------------------------------
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
func getFileCrc32(f *os.File) (uint32, error) {
defer f.Seek(0, 0)
h := crc32.NewIEEE()
_, err := io.Copy(h, f)
return h.Sum32(), err
}

View File

@@ -1,93 +0,0 @@
package rs
import (
. "github.com/qiniu/api.v6/conf"
"github.com/qiniu/rpc"
)
// ----------------------------------------------------------
func (rs Client) Batch(l rpc.Logger, ret interface{}, op []string) (err error) {
return rs.Conn.CallWithForm(l, ret, RS_HOST+"/batch", map[string][]string{"op": op})
}
// ----------------------------------------------------------
// @gist batchStatItemRet
type BatchStatItemRet struct {
Data Entry `json:"data"`
Error string `json:"error"`
Code int `json:"code"`
}
// @endgist
// @gist entryPath
type EntryPath struct {
Bucket string
Key string
}
// @endgist
func (rs Client) BatchStat(l rpc.Logger, entries []EntryPath) (ret []BatchStatItemRet, err error) {
b := make([]string, len(entries))
for i, e := range entries {
b[i] = URIStat(e.Bucket, e.Key)
}
err = rs.Batch(l, &ret, b)
return
}
// ----------------------------------------------------------
// @gist batchItemRet
type BatchItemRet struct {
Error string `json:"error"`
Code int `json:"code"`
}
// @endgist
func (rs Client) BatchDelete(l rpc.Logger, entries []EntryPath) (ret []BatchItemRet, err error) {
b := make([]string, len(entries))
for i, e := range entries {
b[i] = URIDelete(e.Bucket, e.Key)
}
err = rs.Batch(l, &ret, b)
return
}
// ----------------------------------------------------------
// @gist entryPathPair
type EntryPathPair struct {
Src EntryPath
Dest EntryPath
}
// @endgist
func (rs Client) BatchMove(l rpc.Logger, entries []EntryPathPair) (ret []BatchItemRet, err error) {
b := make([]string, len(entries))
for i, e := range entries {
b[i] = URIMove(e.Src.Bucket, e.Src.Key, e.Dest.Bucket, e.Dest.Key)
}
err = rs.Batch(l, &ret, b)
return
}
func (rs Client) BatchCopy(l rpc.Logger, entries []EntryPathPair) (ret []BatchItemRet, err error) {
b := make([]string, len(entries))
for i, e := range entries {
b[i] = URICopy(e.Src.Bucket, e.Src.Key, e.Dest.Bucket, e.Dest.Key)
}
err = rs.Batch(l, &ret, b)
return
}
// ----------------------------------------------------------

View File

@@ -1,93 +0,0 @@
package rs
import (
"encoding/base64"
"net/http"
"github.com/qiniu/api.v6/auth/digest"
. "github.com/qiniu/api.v6/conf"
"github.com/qiniu/rpc"
)
// ----------------------------------------------------------
type Client struct {
Conn rpc.Client
}
func New(mac *digest.Mac) Client {
t := digest.NewTransport(mac, nil)
client := &http.Client{Transport: t}
return Client{rpc.Client{client}}
}
func NewEx(t http.RoundTripper) Client {
client := &http.Client{Transport: t}
return Client{rpc.Client{client}}
}
// ----------------------------------------------------------
// @gist entry
type Entry struct {
Hash string `json:"hash"`
Fsize int64 `json:"fsize"`
PutTime int64 `json:"putTime"`
MimeType string `json:"mimeType"`
Customer string `json:"customer"`
}
// @endgist
func (rs Client) Stat(l rpc.Logger, bucket, key string) (entry Entry, err error) {
err = rs.Conn.Call(l, &entry, RS_HOST+URIStat(bucket, key))
return
}
func (rs Client) Delete(l rpc.Logger, bucket, key string) (err error) {
return rs.Conn.Call(l, nil, RS_HOST+URIDelete(bucket, key))
}
func (rs Client) Move(l rpc.Logger, bucketSrc, keySrc, bucketDest, keyDest string) (err error) {
return rs.Conn.Call(l, nil, RS_HOST+URIMove(bucketSrc, keySrc, bucketDest, keyDest))
}
func (rs Client) Copy(l rpc.Logger, bucketSrc, keySrc, bucketDest, keyDest string) (err error) {
return rs.Conn.Call(l, nil, RS_HOST+URICopy(bucketSrc, keySrc, bucketDest, keyDest))
}
func (rs Client) Fetch(l rpc.Logger, bucket, key, url string) (err error) {
return rs.Conn.Call(l, nil, IO_HOST+URIFetch(bucket, key, url))
}
func (rs Client) ChangeMime(l rpc.Logger, bucket, key, mime string) (err error) {
return rs.Conn.Call(l, nil, RS_HOST+URIChangeMime(bucket, key, mime))
}
func encodeURI(uri string) string {
return base64.URLEncoding.EncodeToString([]byte(uri))
}
func URIDelete(bucket, key string) string {
return "/delete/" + encodeURI(bucket+":"+key)
}
func URIStat(bucket, key string) string {
return "/stat/" + encodeURI(bucket+":"+key)
}
func URICopy(bucketSrc, keySrc, bucketDest, keyDest string) string {
return "/copy/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
}
func URIMove(bucketSrc, keySrc, bucketDest, keyDest string) string {
return "/move/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
}
func URIFetch(bucket, key, url string) string {
return "/fetch/" + encodeURI(url) + "/to/" + encodeURI(bucket+":"+key)
}
func URIChangeMime(bucket, key, mime string) string {
return "/chgm/" + encodeURI(bucket+":"+key) + "/mime/" + encodeURI(mime)
}

View File

@@ -1,74 +0,0 @@
package rs
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/qiniu/api.v6/auth/digest"
"github.com/qiniu/api.v6/url"
)
// ----------------------------------------------------------
type GetPolicy struct {
Expires uint32
}
func (r GetPolicy) MakeRequest(baseUrl string, mac *digest.Mac) (privateUrl string) {
expires := r.Expires
if expires == 0 {
expires = 3600
}
deadline := time.Now().Unix() + int64(expires)
if strings.Contains(baseUrl, "?") {
baseUrl += "&e="
} else {
baseUrl += "?e="
}
baseUrl += strconv.FormatInt(deadline, 10)
token := digest.Sign(mac, []byte(baseUrl))
return baseUrl + "&token=" + token
}
func MakeBaseUrl(domain, key string) (baseUrl string) {
return "http://" + domain + "/" + url.Escape(key)
}
// --------------------------------------------------------------------------------
type PutPolicy struct {
Scope string `json:"scope"`
Expires uint32 `json:"deadline"` // 截止时间(以秒为单位)
InsertOnly uint16 `json:"exclusive,omitempty"` // 若非0, 即使Scope为 Bucket:Key 的形式也是insert only
DetectMime uint16 `json:"detectMime,omitempty"` // 若非0, 则服务端根据内容自动确定 MimeType
FsizeLimit int64 `json:"fsizeLimit,omitempty"`
SaveKey string `json:"saveKey,omitempty"`
CallbackUrl string `json:"callbackUrl,omitempty"`
CallbackBody string `json:"callbackBody,omitempty"`
ReturnUrl string `json:"returnUrl,omitempty"`
ReturnBody string `json:"returnBody,omitempty"`
PersistentOps string `json:"persistentOps,omitempty"`
PersistentNotifyUrl string `json:"persistentNotifyUrl,omitempty"`
PersistentPipeline string `json:"persistentPipeline,omitempty"`
AsyncOps string `json:"asyncOps,omitempty"`
EndUser string `json:"endUser,omitempty"`
MimeLimit string `json:"mimeLimit,omitempty"`
}
func (r *PutPolicy) Token(mac *digest.Mac) string {
var rr = *r
if rr.Expires == 0 {
rr.Expires = 3600
}
rr.Expires += uint32(time.Now().Unix())
b, _ := json.Marshal(&rr)
return digest.SignWithData(mac, b)
}
// ----------------------------------------------------------

View File

@@ -1,87 +0,0 @@
package rsf
import (
"errors"
"io"
"net/http"
"net/url"
"strconv"
"github.com/qiniu/api.v6/auth/digest"
. "github.com/qiniu/api.v6/conf"
"github.com/qiniu/rpc"
)
// ----------------------------------------------------------
type ListItem struct {
Key string `json:"key"`
Hash string `json:"hash"`
Fsize int64 `json:"fsize"`
PutTime int64 `json:"putTime"`
MimeType string `json:"mimeType"`
EndUser string `json:"endUser"`
}
type ListRet struct {
Marker string `json:"marker"`
Items []ListItem `json:"items"`
}
// ----------------------------------------------------------
type Client struct {
Conn rpc.Client
}
func New(mac *digest.Mac) Client {
t := digest.NewTransport(mac, nil)
client := &http.Client{Transport: t}
return Client{rpc.Client{client}}
}
func NewEx(t http.RoundTripper) Client {
client := &http.Client{Transport: t}
return Client{rpc.Client{client}}
}
// ----------------------------------------------------------
// 1. 首次请求 marker = ""
// 2. 无论 err 值如何,均应该先看 entries 是否有内容
// 3. 如果后续没有更多数据err 返回 EOFmarkerOut 返回 ""(但不通过该特征来判断是否结束)
func (rsf Client) ListPrefix(l rpc.Logger, bucket, prefix, marker string, limit int) (entries []ListItem, markerOut string, err error) {
if bucket == "" {
err = errors.New("bucket could not be nil")
return
}
URL := makeListURL(bucket, prefix, marker, limit)
listRet := ListRet{}
err = rsf.Conn.Call(l, &listRet, URL)
if err != nil {
return
}
if listRet.Marker == "" {
return listRet.Items, "", io.EOF
}
return listRet.Items, listRet.Marker, err
}
func makeListURL(bucket, prefix, marker string, limit int) string {
query := make(url.Values)
query.Add("bucket", bucket)
if prefix != "" {
query.Add("prefix", prefix)
}
if marker != "" {
query.Add("marker", marker)
}
if limit > 0 {
query.Add("limit", strconv.FormatInt(int64(limit), 10))
}
return RSF_HOST + "/list?" + query.Encode()
}

View File

@@ -1,208 +0,0 @@
package url
import (
"strconv"
)
type Encoding int
const (
EncodePath Encoding = 1 + iota
EncodeUserPassword
EncodeQueryComponent
EncodeFragment
)
type EscapeError string
func (e EscapeError) Error() string {
return "invalid URL escape " + strconv.Quote(string(e))
}
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
// When 'all' is true the full range of reserved characters are matched.
func shouldEscape(c byte, mode Encoding) bool {
// §2.3 Unreserved characters (alphanum)
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '_', '.', '~': // §2.3 Unreserved characters (mark)
return false
case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch mode {
case EncodePath: // §3.3
// The RFC allows : @ & = + $ but saves / ; , for assigning
// meaning to individual path segments. This package
// only manipulates the path as a whole, so we allow those
// last two as well. That leaves only ? to escape.
return c == '?'
case EncodeUserPassword: // §3.2.2
// The RFC allows ; : & = + $ , in userinfo, so we must escape only @ and /.
// The parsing of userinfo treats : as special so we must escape that too.
return c == '@' || c == '/' || c == ':'
case EncodeQueryComponent: // §3.4
// The RFC reserves (so we must escape) everything.
return true
case EncodeFragment: // §4.1
// The RFC text is silent but the grammar allows
// everything, so escape nothing.
return false
}
}
// Everything else must be escaped.
return true
}
// QueryUnescape does the inverse transformation of QueryEscape, converting
// %AB into the byte 0xAB and '+' into ' ' (space). It returns an error if
// any % is not followed by two hexadecimal digits.
func QueryUnescape(s string) (string, error) {
return UnescapeEx(s, EncodeQueryComponent)
}
func Unescape(s string) (string, error) {
return UnescapeEx(s, EncodePath)
}
// UnescapeEx unescapes a string; the mode specifies
// which section of the URL string is being unescaped.
func UnescapeEx(s string, mode Encoding) (string, error) {
// Count %, check that they're well-formed.
n := 0
hasPlus := false
for i := 0; i < len(s); {
switch s[i] {
case '%':
n++
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
s = s[i:]
if len(s) > 3 {
s = s[0:3]
}
return "", EscapeError(s)
}
i += 3
case '+':
hasPlus = mode == EncodeQueryComponent
i++
default:
i++
}
}
if n == 0 && !hasPlus {
return s, nil
}
t := make([]byte, len(s)-2*n)
j := 0
for i := 0; i < len(s); {
switch s[i] {
case '%':
t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
j++
i += 3
case '+':
if mode == EncodeQueryComponent {
t[j] = ' '
} else {
t[j] = '+'
}
j++
i++
default:
t[j] = s[i]
j++
i++
}
}
return string(t), nil
}
// QueryEscape escapes the string so it can be safely placed
// inside a URL query.
func QueryEscape(s string) string {
return EscapeEx(s, EncodeQueryComponent)
}
func Escape(s string) string {
return EscapeEx(s, EncodePath)
}
func EscapeEx(s string, mode Encoding) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c, mode) {
if c == ' ' && mode == EncodeQueryComponent {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
t := make([]byte, len(s)+2*hexCount)
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && mode == EncodeQueryComponent:
t[j] = '+'
j++
case shouldEscape(c, mode):
t[j] = '%'
t[j+1] = "0123456789ABCDEF"[c>>4]
t[j+2] = "0123456789ABCDEF"[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}

View File

@@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@@ -1,8 +0,0 @@
bytes
=====
[![Build Status](https://drone.io/github.com/qiniu/bytes/status.png)](https://drone.io/github.com/qiniu/bytes/latest)
![logo](http://qiniutek.com/images/logo-2.png)
Extension module of golang bytes processing

View File

@@ -1,177 +0,0 @@
package bytes
import (
"io"
"syscall"
)
// ---------------------------------------------------
type Reader struct {
b []byte
off int
}
func NewReader(val []byte) *Reader {
return &Reader{val, 0}
}
func (r *Reader) Len() int {
if r.off >= len(r.b) {
return 0
}
return len(r.b) - r.off
}
func (r *Reader) Bytes() []byte {
return r.b[r.off:]
}
func (r *Reader) SeekToBegin() (err error) {
r.off = 0
return
}
func (r *Reader) Seek(offset int64, whence int) (ret int64, err error) {
switch whence {
case 0:
case 1:
offset += int64(r.off)
case 2:
offset += int64(len(r.b))
default:
err = syscall.EINVAL
return
}
if offset < 0 {
err = syscall.EINVAL
return
}
if offset >= int64(len(r.b)) {
r.off = len(r.b)
} else {
r.off = int(offset)
}
ret = int64(r.off)
return
}
func (r *Reader) Read(val []byte) (n int, err error) {
n = copy(val, r.b[r.off:])
if n == 0 && len(val) != 0 {
err = io.EOF
return
}
r.off += n
return
}
func (r *Reader) Close() (err error) {
return
}
// ---------------------------------------------------
type Writer struct {
b []byte
n int
}
func NewWriter(buff []byte) *Writer {
return &Writer{buff, 0}
}
func (p *Writer) Write(val []byte) (n int, err error) {
n = copy(p.b[p.n:], val)
if n == 0 && len(val) > 0 {
err = io.EOF
return
}
p.n += n
return
}
func (p *Writer) Len() int {
return p.n
}
func (p *Writer) Bytes() []byte {
return p.b[:p.n]
}
func (p *Writer) Reset() {
p.n = 0
}
// ---------------------------------------------------
type Buffer struct {
b []byte
}
func NewBuffer() *Buffer {
return new(Buffer)
}
func (p *Buffer) ReadAt(buf []byte, off int64) (n int, err error) {
ioff := int(off)
if len(p.b) <= ioff {
return 0, io.EOF
}
n = copy(buf, p.b[ioff:])
if n != len(buf) {
err = io.EOF
}
return
}
func (p *Buffer) WriteAt(buf []byte, off int64) (n int, err error) {
ioff := int(off)
iend := ioff + len(buf)
if len(p.b) < iend {
if len(p.b) == ioff {
p.b = append(p.b, buf...)
return len(buf), nil
}
zero := make([]byte, iend-len(p.b))
p.b = append(p.b, zero...)
}
copy(p.b[ioff:], buf)
return len(buf), nil
}
func (p *Buffer) WriteStringAt(buf string, off int64) (n int, err error) {
ioff := int(off)
iend := ioff + len(buf)
if len(p.b) < iend {
if len(p.b) == ioff {
p.b = append(p.b, buf...)
return len(buf), nil
}
zero := make([]byte, iend-len(p.b))
p.b = append(p.b, zero...)
}
copy(p.b[ioff:], buf)
return len(buf), nil
}
func (p *Buffer) Truncate(fsize int64) (err error) {
size := int(fsize)
if len(p.b) < size {
zero := make([]byte, size-len(p.b))
p.b = append(p.b, zero...)
} else {
p.b = p.b[:size]
}
return nil
}
func (p *Buffer) Buffer() []byte {
return p.b
}
func (p *Buffer) Len() int {
return len(p.b)
}
// ---------------------------------------------------

View File

@@ -1,62 +0,0 @@
package seekable
import (
"errors"
"io"
"io/ioutil"
"net/http"
"github.com/qiniu/bytes"
)
// ---------------------------------------------------
type Seekabler interface {
Bytes() []byte
Read(val []byte) (n int, err error)
SeekToBegin() error
}
type SeekableCloser interface {
Seekabler
io.Closer
}
// ---------------------------------------------------
type readCloser struct {
Seekabler
io.Closer
}
var ErrNoBody = errors.New("no body")
func New(req *http.Request) (r SeekableCloser, err error) {
if req.Body == nil {
return nil, ErrNoBody
}
var ok bool
if r, ok = req.Body.(SeekableCloser); ok {
return
}
b, err2 := ReadAll(req)
if err2 != nil {
return nil, err2
}
r = bytes.NewReader(b)
req.Body = readCloser{r, req.Body}
return
}
func ReadAll(req *http.Request) (b []byte, err error) {
if req.ContentLength > 0 {
b = make([]byte, int(req.ContentLength))
_, err = io.ReadFull(req.Body, b)
return
} else if req.ContentLength == 0 {
return nil, ErrNoBody
}
return ioutil.ReadAll(req.Body)
}
// ---------------------------------------------------

View File

@@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@@ -1,23 +0,0 @@
#CHANGELOG
## v2.0.01
2013-09-10 Issue [#7](https://github.com/qiniu/rpc/pull/7):
- support jsonrpc
## v2.0.00
2013-03-20 Issue [#5](https://github.com/qiniu/rpc/pull/5):
- support X-Reqid, X-Log
- support User-Agent
- rpc error bugfix
## v1.0.00
2013-03-10 Issue [#2](https://github.com/qiniu/rpc/pull/2):
- initial version

View File

@@ -1,8 +0,0 @@
rpc
===
[![Build Status](https://drone.io/github.com/qiniu/rpc/status.png)](https://drone.io/github.com/qiniu/rpc/latest)
[![Qiniu Logo](http://qiniutek.com/images/logo-2.png)](http://qiniu.com/)
Golang rpc client based on http

View File

@@ -1,207 +0,0 @@
package rpc
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"strings"
)
var UserAgent = "Golang qiniu/rpc package"
// --------------------------------------------------------------------
type Client struct {
*http.Client
}
var DefaultClient = Client{http.DefaultClient}
// --------------------------------------------------------------------
type Logger interface {
ReqId() string
Xput(logs []string)
}
// --------------------------------------------------------------------
func (r Client) Get(l Logger, url string) (resp *http.Response, err error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return
}
return r.Do(l, req)
}
func (r Client) PostWith(
l Logger, url1 string, bodyType string, body io.Reader, bodyLength int) (resp *http.Response, err error) {
req, err := http.NewRequest("POST", url1, body)
if err != nil {
return
}
req.Header.Set("Content-Type", bodyType)
req.ContentLength = int64(bodyLength)
return r.Do(l, req)
}
func (r Client) PostWith64(
l Logger, url1 string, bodyType string, body io.Reader, bodyLength int64) (resp *http.Response, err error) {
req, err := http.NewRequest("POST", url1, body)
if err != nil {
return
}
req.Header.Set("Content-Type", bodyType)
req.ContentLength = bodyLength
return r.Do(l, req)
}
func (r Client) PostWithForm(
l Logger, url1 string, data map[string][]string) (resp *http.Response, err error) {
msg := url.Values(data).Encode()
return r.PostWith(l, url1, "application/x-www-form-urlencoded", strings.NewReader(msg), len(msg))
}
func (r Client) PostWithJson(
l Logger, url1 string, data interface{}) (resp *http.Response, err error) {
msg, err := json.Marshal(data)
if err != nil {
return
}
return r.PostWith(l, url1, "application/json", bytes.NewReader(msg), len(msg))
}
func (r Client) Do(l Logger, req *http.Request) (resp *http.Response, err error) {
if l != nil {
req.Header.Set("X-Reqid", l.ReqId())
}
req.Header.Set("User-Agent", UserAgent)
resp, err = r.Client.Do(req)
if err != nil {
return
}
if l != nil {
details := resp.Header["X-Log"]
if len(details) > 0 {
l.Xput(details)
}
}
return
}
// --------------------------------------------------------------------
type ErrorInfo struct {
Err string `json:"error"`
Reqid string `json:"reqid"`
Details []string `json:"details"`
Code int `json:"code"`
}
func (r *ErrorInfo) Error() string {
msg, _ := json.Marshal(r)
return string(msg)
}
// --------------------------------------------------------------------
type errorRet struct {
Error string `json:"error"`
}
func ResponseError(resp *http.Response) (err error) {
e := &ErrorInfo{
Details: resp.Header["X-Log"],
Reqid: resp.Header.Get("X-Reqid"),
Code: resp.StatusCode,
}
if resp.StatusCode > 299 {
if resp.ContentLength != 0 {
if ct, ok := resp.Header["Content-Type"]; ok && ct[0] == "application/json" {
var ret1 errorRet
json.NewDecoder(resp.Body).Decode(&ret1)
e.Err = ret1.Error
}
}
}
return e
}
func callRet(l Logger, ret interface{}, resp *http.Response) (err error) {
defer resp.Body.Close()
if resp.StatusCode/100 == 2 {
if ret != nil && resp.ContentLength != 0 {
err = json.NewDecoder(resp.Body).Decode(ret)
if err != nil {
return
}
}
if resp.StatusCode == 200 {
return nil
}
}
return ResponseError(resp)
}
func (r Client) CallWithForm(l Logger, ret interface{}, url1 string, param map[string][]string) (err error) {
resp, err := r.PostWithForm(l, url1, param)
if err != nil {
return err
}
return callRet(l, ret, resp)
}
func (r Client) CallWithJson(l Logger, ret interface{}, url1 string, param interface{}) (err error) {
resp, err := r.PostWithJson(l, url1, param)
if err != nil {
return err
}
return callRet(l, ret, resp)
}
func (r Client) CallWith(
l Logger, ret interface{}, url1 string, bodyType string, body io.Reader, bodyLength int) (err error) {
resp, err := r.PostWith(l, url1, bodyType, body, bodyLength)
if err != nil {
return err
}
return callRet(l, ret, resp)
}
func (r Client) CallWith64(
l Logger, ret interface{}, url1 string, bodyType string, body io.Reader, bodyLength int64) (err error) {
resp, err := r.PostWith64(l, url1, bodyType, body, bodyLength)
if err != nil {
return err
}
return callRet(l, ret, resp)
}
func (r Client) Call(
l Logger, ret interface{}, url1 string) (err error) {
resp, err := r.PostWith(l, url1, "application/x-www-form-urlencoded", nil, 0)
if err != nil {
return err
}
return callRet(l, ret, resp)
}
// --------------------------------------------------------------------

158
vendor/goftp.io/server/driver.go generated vendored
View File

@@ -1,158 +0,0 @@
// Copyright 2018 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
import (
"errors"
"io"
"strings"
)
// DriverFactory is a driver factory to create driver. For each client that connects to the server, a new FTPDriver is required.
// Create an implementation if this interface and provide it to FTPServer.
type DriverFactory interface {
NewDriver() (Driver, error)
}
// Driver is an interface that you will create an implementation that speaks to your
// chosen persistence layer. graval will create a new instance of your
// driver for each client that connects and delegate to it as required.
type Driver interface {
// params - a file path
// returns - a time indicating when the requested path was last modified
// - an error if the file doesn't exist or the user lacks
// permissions
Stat(string) (FileInfo, error)
// params - path, function on file or subdir found
// returns - error
// path
ListDir(string, func(FileInfo) error) error
// params - path
// returns - nil if the directory was deleted or any error encountered
DeleteDir(string) error
// params - path
// returns - nil if the file was deleted or any error encountered
DeleteFile(string) error
// params - from_path, to_path
// returns - nil if the file was renamed or any error encountered
Rename(string, string) error
// params - path
// returns - nil if the new directory was created or any error encountered
MakeDir(string) error
// params - path
// returns - a string containing the file data to send to the client
GetFile(string, int64) (int64, io.ReadCloser, error)
// params - destination path, an io.Reader containing the file data
// returns - the number of bytes writen and the first error encountered while writing, if any.
PutFile(string, io.Reader, bool) (int64, error)
}
var _ Driver = &MultipleDriver{}
// MultipleDriver represents a composite driver
type MultipleDriver struct {
drivers map[string]Driver
}
// Stat implements Driver
func (driver *MultipleDriver) Stat(path string) (FileInfo, error) {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.Stat(strings.TrimPrefix(path, prefix))
}
}
return nil, errors.New("Not a file")
}
// ListDir implements Driver
func (driver *MultipleDriver) ListDir(path string, callback func(FileInfo) error) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.ListDir(strings.TrimPrefix(path, prefix), callback)
}
}
return errors.New("Not a directory")
}
// DeleteDir implements Driver
func (driver *MultipleDriver) DeleteDir(path string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.DeleteDir(strings.TrimPrefix(path, prefix))
}
}
return errors.New("Not a directory")
}
// DeleteFile implements Driver
func (driver *MultipleDriver) DeleteFile(path string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.DeleteFile(strings.TrimPrefix(path, prefix))
}
}
return errors.New("Not a file")
}
// Rename implements Driver
func (driver *MultipleDriver) Rename(fromPath string, toPath string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(fromPath, prefix) {
return driver.Rename(strings.TrimPrefix(fromPath, prefix), strings.TrimPrefix(toPath, prefix))
}
}
return errors.New("Not a file")
}
// MakeDir implements Driver
func (driver *MultipleDriver) MakeDir(path string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.MakeDir(strings.TrimPrefix(path, prefix))
}
}
return errors.New("Not a directory")
}
// GetFile implements Driver
func (driver *MultipleDriver) GetFile(path string, offset int64) (int64, io.ReadCloser, error) {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.GetFile(strings.TrimPrefix(path, prefix), offset)
}
}
return 0, nil, errors.New("Not a file")
}
// PutFile implements Driver
func (driver *MultipleDriver) PutFile(destPath string, data io.Reader, appendData bool) (int64, error) {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(destPath, prefix) {
return driver.PutFile(strings.TrimPrefix(destPath, prefix), data, appendData)
}
}
return 0, errors.New("Not a file")
}
// MultipleDriverFactory implements a DriverFactory
type MultipleDriverFactory struct {
drivers map[string]Driver
}
// NewDriver implements DriverFactory
func (factory *MultipleDriverFactory) NewDriver() (Driver, error) {
return &MultipleDriver{factory.drivers}, nil
}

176
vendor/goftp.io/server/notifier.go generated vendored
View File

@@ -1,176 +0,0 @@
// Copyright 2020 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
// Notifier represents a notification operator interface
type Notifier interface {
BeforeLoginUser(conn *Conn, userName string)
BeforePutFile(conn *Conn, dstPath string)
BeforeDeleteFile(conn *Conn, dstPath string)
BeforeChangeCurDir(conn *Conn, oldCurDir, newCurDir string)
BeforeCreateDir(conn *Conn, dstPath string)
BeforeDeleteDir(conn *Conn, dstPath string)
BeforeDownloadFile(conn *Conn, dstPath string)
AfterUserLogin(conn *Conn, userName, password string, passMatched bool, err error)
AfterFilePut(conn *Conn, dstPath string, size int64, err error)
AfterFileDeleted(conn *Conn, dstPath string, err error)
AfterFileDownloaded(conn *Conn, dstPath string, size int64, err error)
AfterCurDirChanged(conn *Conn, oldCurDir, newCurDir string, err error)
AfterDirCreated(conn *Conn, dstPath string, err error)
AfterDirDeleted(conn *Conn, dstPath string, err error)
}
type notifierList []Notifier
var (
_ Notifier = notifierList{}
)
func (notifiers notifierList) BeforeLoginUser(conn *Conn, userName string) {
for _, notifier := range notifiers {
notifier.BeforeLoginUser(conn, userName)
}
}
func (notifiers notifierList) BeforePutFile(conn *Conn, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforePutFile(conn, dstPath)
}
}
func (notifiers notifierList) BeforeDeleteFile(conn *Conn, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeDeleteFile(conn, dstPath)
}
}
func (notifiers notifierList) BeforeChangeCurDir(conn *Conn, oldCurDir, newCurDir string) {
for _, notifier := range notifiers {
notifier.BeforeChangeCurDir(conn, oldCurDir, newCurDir)
}
}
func (notifiers notifierList) BeforeCreateDir(conn *Conn, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeCreateDir(conn, dstPath)
}
}
func (notifiers notifierList) BeforeDeleteDir(conn *Conn, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeDeleteDir(conn, dstPath)
}
}
func (notifiers notifierList) BeforeDownloadFile(conn *Conn, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeDownloadFile(conn, dstPath)
}
}
func (notifiers notifierList) AfterUserLogin(conn *Conn, userName, password string, passMatched bool, err error) {
for _, notifier := range notifiers {
notifier.AfterUserLogin(conn, userName, password, passMatched, err)
}
}
func (notifiers notifierList) AfterFilePut(conn *Conn, dstPath string, size int64, err error) {
for _, notifier := range notifiers {
notifier.AfterFilePut(conn, dstPath, size, err)
}
}
func (notifiers notifierList) AfterFileDeleted(conn *Conn, dstPath string, err error) {
for _, notifier := range notifiers {
notifier.AfterFileDeleted(conn, dstPath, err)
}
}
func (notifiers notifierList) AfterFileDownloaded(conn *Conn, dstPath string, size int64, err error) {
for _, notifier := range notifiers {
notifier.AfterFileDownloaded(conn, dstPath, size, err)
}
}
func (notifiers notifierList) AfterCurDirChanged(conn *Conn, oldCurDir, newCurDir string, err error) {
for _, notifier := range notifiers {
notifier.AfterCurDirChanged(conn, oldCurDir, newCurDir, err)
}
}
func (notifiers notifierList) AfterDirCreated(conn *Conn, dstPath string, err error) {
for _, notifier := range notifiers {
notifier.AfterDirCreated(conn, dstPath, err)
}
}
func (notifiers notifierList) AfterDirDeleted(conn *Conn, dstPath string, err error) {
for _, notifier := range notifiers {
notifier.AfterDirDeleted(conn, dstPath, err)
}
}
// NullNotifier implements Notifier
type NullNotifier struct{}
var (
_ Notifier = &NullNotifier{}
)
// BeforeLoginUser implements Notifier
func (NullNotifier) BeforeLoginUser(conn *Conn, userName string) {
}
// BeforePutFile implements Notifier
func (NullNotifier) BeforePutFile(conn *Conn, dstPath string) {
}
// BeforeDeleteFile implements Notifier
func (NullNotifier) BeforeDeleteFile(conn *Conn, dstPath string) {
}
// BeforeChangeCurDir implements Notifier
func (NullNotifier) BeforeChangeCurDir(conn *Conn, oldCurDir, newCurDir string) {
}
// BeforeCreateDir implements Notifier
func (NullNotifier) BeforeCreateDir(conn *Conn, dstPath string) {
}
// BeforeDeleteDir implements Notifier
func (NullNotifier) BeforeDeleteDir(conn *Conn, dstPath string) {
}
// BeforeDownloadFile implements Notifier
func (NullNotifier) BeforeDownloadFile(conn *Conn, dstPath string) {
}
// AfterUserLogin implements Notifier
func (NullNotifier) AfterUserLogin(conn *Conn, userName, password string, passMatched bool, err error) {
}
// AfterFilePut implements Notifier
func (NullNotifier) AfterFilePut(conn *Conn, dstPath string, size int64, err error) {
}
// AfterFileDeleted implements Notifier
func (NullNotifier) AfterFileDeleted(conn *Conn, dstPath string, err error) {
}
// AfterFileDownloaded implements Notifier
func (NullNotifier) AfterFileDownloaded(conn *Conn, dstPath string, size int64, err error) {
}
// AfterCurDirChanged implements Notifier
func (NullNotifier) AfterCurDirChanged(conn *Conn, oldCurDir, newCurDir string, err error) {
}
// AfterDirCreated implements Notifier
func (NullNotifier) AfterDirCreated(conn *Conn, dstPath string, err error) {
}
// AfterDirDeleted implements Notifier
func (NullNotifier) AfterDirDeleted(conn *Conn, dstPath string, err error) {
}

View File

@@ -13,6 +13,14 @@ steps:
exclude:
- pull_request
- name: linter
image: golang:1.14
commands:
- go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.30.0
- golangci-lint run
environment:
GOPROXY: https://goproxy.cn,direct
- name: test
pull: always
image: golang:1.13

View File

@@ -1,4 +1,5 @@
testdata
coverage.txt
exampleftpd/exampleftpd
.vscode
.vscode
*~

View File

View File

@@ -5,33 +5,25 @@
A FTP server framework forked from [github.com/yob/graval](http://github.com/yob/graval) and changed a lot.
Full documentation for the package is available on [godoc](http://godoc.org/goftp.io/server)
## Version
v0.2.3
Full documentation for the package is available on [godoc](http://pkg.go.dev/goftp.io/server)
## Installation
go get goftp.io/server
go get goftp.io/server/v1
## Usage
To boot a FTP server you will need to provide a driver that speaks to
your persistence layer - the required driver contract is in [the
documentation](http://godoc.org/goftp.io/server).
documentation](http://pkg.go.dev/goftp.io/server).
Look at the [file driver](https://gitea.com/goftp/file-driver) to see
Look at the [file driver](https://goftp.io/server/driver/file) to see
an example of how to build a backend.
There is a [sample ftp server](/exampleftpd) as a demo. You can build it with this
There is a [sample ftp server](https://goftp.io/ftpd) as a demo. You can build it with this
command:
go install goftp.io/server/exampleftpd
Then run it if you have add $GOPATH to your $PATH:
exampleftpd -root /tmp
go install goftp.io/ftpd
And finally, connect to the server with any FTP client and the following
details:
@@ -43,6 +35,10 @@ details:
This uses the file driver mentioned above to serve files.
## Contact us
You can contact us via discord [https://discord.gg/ytmYqfNfqh](https://discord.gg/ytmYqfNfqh) or QQ群 972357369
## Contributors
see [https://gitea.com/goftp/server/graphs/contributors](https://gitea.com/goftp/server/graphs/contributors)

View File

@@ -10,7 +10,7 @@ import (
// Auth is an interface to auth your ftp user login.
type Auth interface {
CheckPasswd(string, string) (bool, error)
CheckPasswd(*Context, string, string) (bool, error)
}
var (
@@ -24,7 +24,7 @@ type SimpleAuth struct {
}
// CheckPasswd will check user's password
func (a *SimpleAuth) CheckPasswd(name, pass string) (bool, error) {
func (a *SimpleAuth) CheckPasswd(ctx *Context, name, pass string) (bool, error) {
return constantTimeEquals(name, a.Name) && constantTimeEquals(pass, a.Password), nil
}

View File

File diff suppressed because it is too large Load Diff

12
vendor/goftp.io/server/v2/context.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2020 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
// Context represents a context the driver may want to know
type Context struct {
Sess *Session
Cmd string // request command on this request
Param string // request param on this request
}

View File

@@ -15,6 +15,8 @@ import (
"sync"
"syscall"
"time"
"goftp.io/server/v2/ratelimit"
)
// DataSocket describes a data socket is used to send non-control data between the client and
@@ -37,75 +39,80 @@ type DataSocket interface {
Close() error
}
type ftpActiveSocket struct {
conn *net.TCPConn
host string
port int
logger Logger
type activeSocket struct {
conn *net.TCPConn
reader io.Reader
writer io.Writer
sess *Session
host string
port int
}
func newActiveSocket(remote string, port int, logger Logger, sessionID string) (DataSocket, error) {
func newActiveSocket(sess *Session, remote string, port int) (DataSocket, error) {
connectTo := net.JoinHostPort(remote, strconv.Itoa(port))
logger.Print(sessionID, "Opening active data connection to "+connectTo)
sess.log("Opening active data connection to " + connectTo)
raddr, err := net.ResolveTCPAddr("tcp", connectTo)
if err != nil {
logger.Print(sessionID, err)
sess.log(err)
return nil, err
}
tcpConn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
logger.Print(sessionID, err)
sess.log(err)
return nil, err
}
socket := new(ftpActiveSocket)
socket := new(activeSocket)
socket.sess = sess
socket.conn = tcpConn
socket.reader = ratelimit.Reader(tcpConn, sess.server.rateLimiter)
socket.writer = ratelimit.Writer(tcpConn, sess.server.rateLimiter)
socket.host = remote
socket.port = port
socket.logger = logger
return socket, nil
}
func (socket *ftpActiveSocket) Host() string {
func (socket *activeSocket) Host() string {
return socket.host
}
func (socket *ftpActiveSocket) Port() int {
func (socket *activeSocket) Port() int {
return socket.port
}
func (socket *ftpActiveSocket) Read(p []byte) (n int, err error) {
return socket.conn.Read(p)
func (socket *activeSocket) Read(p []byte) (n int, err error) {
return socket.reader.Read(p)
}
func (socket *ftpActiveSocket) ReadFrom(r io.Reader) (int64, error) {
return socket.conn.ReadFrom(r)
func (socket *activeSocket) ReadFrom(r io.Reader) (int64, error) {
return io.Copy(socket.writer, r)
}
func (socket *ftpActiveSocket) Write(p []byte) (n int, err error) {
return socket.conn.Write(p)
func (socket *activeSocket) Write(p []byte) (n int, err error) {
return socket.writer.Write(p)
}
func (socket *ftpActiveSocket) Close() error {
func (socket *activeSocket) Close() error {
return socket.conn.Close()
}
type ftpPassiveSocket struct {
conn net.Conn
port int
host string
ingress chan []byte
egress chan []byte
logger Logger
lock sync.Mutex // protects conn and err
err error
tlsConfig *tls.Config
type passiveSocket struct {
sess *Session
conn net.Conn
reader io.Reader
writer io.Writer
port int
host string
ingress chan []byte
egress chan []byte
lock sync.Mutex // protects conn and err
err error
}
// Detect if an error is "bind: address already in use"
@@ -134,46 +141,46 @@ func isErrorAddressAlreadyInUse(err error) bool {
return false
}
func (conn *Conn) newPassiveSocket() (DataSocket, error) {
socket := new(ftpPassiveSocket)
func (sess *Session) newPassiveSocket() (DataSocket, error) {
socket := new(passiveSocket)
socket.ingress = make(chan []byte)
socket.egress = make(chan []byte)
socket.logger = conn.logger
socket.host = conn.passiveListenIP()
socket.tlsConfig = conn.tlsConfig
socket.sess = sess
socket.host = sess.passiveListenIP()
const retries = 10
var err error
for i := 1; i <= retries; i++ {
socket.port = conn.PassivePort()
err = socket.GoListenAndServe(conn.sessionID)
socket.port = sess.PassivePort()
err = socket.ListenAndServe()
if err != nil && socket.port != 0 && isErrorAddressAlreadyInUse(err) {
// choose a different port on error already in use
continue
}
break
}
conn.dataConn = socket
sess.dataConn = socket
return socket, err
}
func (socket *ftpPassiveSocket) Host() string {
func (socket *passiveSocket) Host() string {
return socket.host
}
func (socket *ftpPassiveSocket) Port() int {
func (socket *passiveSocket) Port() int {
return socket.port
}
func (socket *ftpPassiveSocket) Read(p []byte) (n int, err error) {
func (socket *passiveSocket) Read(p []byte) (n int, err error) {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.err != nil {
return 0, socket.err
}
return socket.conn.Read(p)
return socket.reader.Read(p)
}
func (socket *ftpPassiveSocket) ReadFrom(r io.Reader) (int64, error) {
func (socket *passiveSocket) ReadFrom(r io.Reader) (int64, error) {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.err != nil {
@@ -182,19 +189,19 @@ func (socket *ftpPassiveSocket) ReadFrom(r io.Reader) (int64, error) {
// For normal TCPConn, this will use sendfile syscall; if not,
// it will just downgrade to normal read/write procedure
return io.Copy(socket.conn, r)
return io.Copy(socket.writer, r)
}
func (socket *ftpPassiveSocket) Write(p []byte) (n int, err error) {
func (socket *passiveSocket) Write(p []byte) (n int, err error) {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.err != nil {
return 0, socket.err
}
return socket.conn.Write(p)
return socket.writer.Write(p)
}
func (socket *ftpPassiveSocket) Close() error {
func (socket *passiveSocket) Close() error {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.conn != nil {
@@ -203,18 +210,18 @@ func (socket *ftpPassiveSocket) Close() error {
return nil
}
func (socket *ftpPassiveSocket) GoListenAndServe(sessionID string) (err error) {
func (socket *passiveSocket) ListenAndServe() (err error) {
laddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort("", strconv.Itoa(socket.port)))
if err != nil {
socket.logger.Print(sessionID, err)
return
socket.sess.log(err)
return err
}
var tcplistener *net.TCPListener
tcplistener, err = net.ListenTCP("tcp", laddr)
if err != nil {
socket.logger.Print(sessionID, err)
return
socket.sess.log(err)
return err
}
// The timeout, for a remote client to establish connection
@@ -222,8 +229,8 @@ func (socket *ftpPassiveSocket) GoListenAndServe(sessionID string) (err error) {
const acceptTimeout = 60 * time.Second
err = tcplistener.SetDeadline(time.Now().Add(acceptTimeout))
if err != nil {
socket.logger.Print(sessionID, err)
return
socket.sess.log(err)
return err
}
var listener net.Listener = tcplistener
@@ -231,13 +238,13 @@ func (socket *ftpPassiveSocket) GoListenAndServe(sessionID string) (err error) {
parts := strings.Split(add.String(), ":")
port, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
socket.logger.Print(sessionID, err)
return
socket.sess.log(err)
return err
}
socket.port = port
if socket.tlsConfig != nil {
listener = tls.NewListener(listener, socket.tlsConfig)
if socket.sess.server.tlsConfig != nil {
listener = tls.NewListener(listener, socket.sess.server.tlsConfig)
}
socket.lock.Lock()
@@ -251,6 +258,8 @@ func (socket *ftpPassiveSocket) GoListenAndServe(sessionID string) (err error) {
}
socket.err = nil
socket.conn = conn
socket.reader = ratelimit.Reader(socket.conn, socket.sess.server.rateLimiter)
socket.writer = ratelimit.Writer(socket.conn, socket.sess.server.rateLimiter)
_ = listener.Close()
}()
return nil

View File

162
vendor/goftp.io/server/v2/driver.go generated vendored Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2018 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
import (
"errors"
"io"
"os"
"strings"
)
// FileInfo represents an file interface
type FileInfo interface {
os.FileInfo
Owner() string
Group() string
}
// Driver is an interface that you will implement to create a driver for your
// chosen persistence layer. The server will create a new instance of your
// driver for each client that connects and delegate to it as required.
//
// Note that if the driver also implements the Auth interface then
// this will be called instead of calling Options.Auth. This allows
// the Auth mechanism to change the driver configuration.
type Driver interface {
// params - a file path
// returns - a time indicating when the requested path was last modified
// - an error if the file doesn't exist or the user lacks
// permissions
Stat(*Context, string) (os.FileInfo, error)
// params - path, function on file or subdir found
// returns - error
// path
ListDir(*Context, string, func(os.FileInfo) error) error
// params - path
// returns - nil if the directory was deleted or any error encountered
DeleteDir(*Context, string) error
// params - path
// returns - nil if the file was deleted or any error encountered
DeleteFile(*Context, string) error
// params - from_path, to_path
// returns - nil if the file was renamed or any error encountered
Rename(*Context, string, string) error
// params - path
// returns - nil if the new directory was created or any error encountered
MakeDir(*Context, string) error
// params - path, filepos
// returns - a string containing the file data to send to the client
GetFile(*Context, string, int64) (int64, io.ReadCloser, error)
// params - destination path, an io.Reader containing the file data
// returns - the number of bytes written and the first error encountered while writing, if any.
PutFile(*Context, string, io.Reader, int64) (int64, error)
}
var _ Driver = &MultiDriver{}
// MultiDriver represents a composite driver
type MultiDriver struct {
drivers map[string]Driver
}
// NewMultiDriver creates a multi driver to combind multiple driver
func NewMultiDriver(drivers map[string]Driver) Driver {
return &MultiDriver{
drivers: drivers,
}
}
// Stat implements Driver
func (driver *MultiDriver) Stat(ctx *Context, path string) (os.FileInfo, error) {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.Stat(ctx, strings.TrimPrefix(path, prefix))
}
}
return nil, errors.New("Not a file")
}
// ListDir implements Driver
func (driver *MultiDriver) ListDir(ctx *Context, path string, callback func(os.FileInfo) error) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.ListDir(ctx, strings.TrimPrefix(path, prefix), callback)
}
}
return errors.New("Not a directory")
}
// DeleteDir implements Driver
func (driver *MultiDriver) DeleteDir(ctx *Context, path string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.DeleteDir(ctx, strings.TrimPrefix(path, prefix))
}
}
return errors.New("Not a directory")
}
// DeleteFile implements Driver
func (driver *MultiDriver) DeleteFile(ctx *Context, path string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.DeleteFile(ctx, strings.TrimPrefix(path, prefix))
}
}
return errors.New("Not a file")
}
// Rename implements Driver
func (driver *MultiDriver) Rename(ctx *Context, fromPath string, toPath string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(fromPath, prefix) {
return driver.Rename(ctx, strings.TrimPrefix(fromPath, prefix), strings.TrimPrefix(toPath, prefix))
}
}
return errors.New("Not a file")
}
// MakeDir implements Driver
func (driver *MultiDriver) MakeDir(ctx *Context, path string) error {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.MakeDir(ctx, strings.TrimPrefix(path, prefix))
}
}
return errors.New("Not a directory")
}
// GetFile implements Driver
func (driver *MultiDriver) GetFile(ctx *Context, path string, offset int64) (int64, io.ReadCloser, error) {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(path, prefix) {
return driver.GetFile(ctx, strings.TrimPrefix(path, prefix), offset)
}
}
return 0, nil, errors.New("Not a file")
}
// PutFile implements Driver
func (driver *MultiDriver) PutFile(ctx *Context, destPath string, data io.Reader, offset int64) (int64, error) {
for prefix, driver := range driver.drivers {
if strings.HasPrefix(destPath, prefix) {
return driver.PutFile(ctx, strings.TrimPrefix(destPath, prefix), data, offset)
}
}
return 0, errors.New("Not a file")
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
package file
import (
"errors"
@@ -11,50 +11,42 @@ import (
"os"
"path/filepath"
"strings"
"goftp.io/server/v2"
)
// FileDriver implements Driver directly read local file system
type FileDriver struct {
// Driver implements Driver directly read local file system
type Driver struct {
RootPath string
Perm
}
func (driver *FileDriver) realPath(path string) string {
// NewDriver implements Driver
func NewDriver(rootPath string) (server.Driver, error) {
var err error
rootPath, err = filepath.Abs(rootPath)
if err != nil {
return nil, err
}
return &Driver{rootPath}, nil
}
func (driver *Driver) realPath(path string) string {
paths := strings.Split(path, "/")
return filepath.Join(append([]string{driver.RootPath}, paths...)...)
}
// Stat implements Driver
func (driver *FileDriver) Stat(path string) (FileInfo, error) {
func (driver *Driver) Stat(ctx *server.Context, path string) (os.FileInfo, error) {
basepath := driver.realPath(path)
rPath, err := filepath.Abs(basepath)
if err != nil {
return nil, err
}
f, err := os.Lstat(rPath)
if err != nil {
return nil, err
}
mode, err := driver.Perm.GetMode(path)
if err != nil {
return nil, err
}
if f.IsDir() {
mode |= os.ModeDir
}
owner, err := driver.Perm.GetOwner(path)
if err != nil {
return nil, err
}
group, err := driver.Perm.GetGroup(path)
if err != nil {
return nil, err
}
return &fileInfo{f, mode, owner, group}, nil
return os.Lstat(rPath)
}
// ListDir implements Driver
func (driver *FileDriver) ListDir(path string, callback func(FileInfo) error) error {
func (driver *Driver) ListDir(ctx *server.Context, path string, callback func(os.FileInfo) error) error {
basepath := driver.realPath(path)
return filepath.Walk(basepath, func(f string, info os.FileInfo, err error) error {
if err != nil {
@@ -62,22 +54,7 @@ func (driver *FileDriver) ListDir(path string, callback func(FileInfo) error) er
}
rPath, _ := filepath.Rel(basepath, f)
if rPath == info.Name() {
mode, err := driver.Perm.GetMode(rPath)
if err != nil {
return err
}
if info.IsDir() {
mode |= os.ModeDir
}
owner, err := driver.Perm.GetOwner(rPath)
if err != nil {
return err
}
group, err := driver.Perm.GetGroup(rPath)
if err != nil {
return err
}
err = callback(&fileInfo{info, mode, owner, group})
err = callback(info)
if err != nil {
return err
}
@@ -90,7 +67,7 @@ func (driver *FileDriver) ListDir(path string, callback func(FileInfo) error) er
}
// DeleteDir implements Driver
func (driver *FileDriver) DeleteDir(path string) error {
func (driver *Driver) DeleteDir(ctx *server.Context, path string) error {
rPath := driver.realPath(path)
f, err := os.Lstat(rPath)
if err != nil {
@@ -103,7 +80,7 @@ func (driver *FileDriver) DeleteDir(path string) error {
}
// DeleteFile implements Driver
func (driver *FileDriver) DeleteFile(path string) error {
func (driver *Driver) DeleteFile(ctx *server.Context, path string) error {
rPath := driver.realPath(path)
f, err := os.Lstat(rPath)
if err != nil {
@@ -116,38 +93,46 @@ func (driver *FileDriver) DeleteFile(path string) error {
}
// Rename implements Driver
func (driver *FileDriver) Rename(fromPath string, toPath string) error {
func (driver *Driver) Rename(ctx *server.Context, fromPath string, toPath string) error {
oldPath := driver.realPath(fromPath)
newPath := driver.realPath(toPath)
return os.Rename(oldPath, newPath)
}
// MakeDir implements Driver
func (driver *FileDriver) MakeDir(path string) error {
func (driver *Driver) MakeDir(ctx *server.Context, path string) error {
rPath := driver.realPath(path)
return os.MkdirAll(rPath, os.ModePerm)
}
// GetFile implements Driver
func (driver *FileDriver) GetFile(path string, offset int64) (int64, io.ReadCloser, error) {
func (driver *Driver) GetFile(ctx *server.Context, path string, offset int64) (int64, io.ReadCloser, error) {
rPath := driver.realPath(path)
f, err := os.Open(rPath)
if err != nil {
return 0, nil, err
}
defer func() {
if err != nil && f != nil {
f.Close()
}
}()
info, err := f.Stat()
if err != nil {
return 0, nil, err
}
f.Seek(offset, io.SeekStart)
_, err = f.Seek(offset, io.SeekStart)
if err != nil {
return 0, nil, err
}
return info.Size() - offset, f, nil
}
// PutFile implements Driver
func (driver *FileDriver) PutFile(destPath string, data io.Reader, appendData bool) (int64, error) {
func (driver *Driver) PutFile(ctx *server.Context, destPath string, data io.Reader, offset int64) (int64, error) {
rPath := driver.realPath(destPath)
var isExist bool
f, err := os.Lstat(rPath)
@@ -164,11 +149,11 @@ func (driver *FileDriver) PutFile(destPath string, data io.Reader, appendData bo
}
}
if appendData && !isExist {
appendData = false
if offset > -1 && !isExist {
offset = -1
}
if !appendData {
if offset == -1 {
if isExist {
err = os.Remove(rPath)
if err != nil {
@@ -193,7 +178,15 @@ func (driver *FileDriver) PutFile(destPath string, data io.Reader, appendData bo
}
defer of.Close()
_, err = of.Seek(0, os.SEEK_END)
info, err := of.Stat()
if err != nil {
return 0, err
}
if offset > info.Size() {
return 0, fmt.Errorf("Offset %d is beyond file size %d", offset, info.Size())
}
_, err = of.Seek(offset, os.SEEK_END)
if err != nil {
return 0, err
}
@@ -205,19 +198,3 @@ func (driver *FileDriver) PutFile(destPath string, data io.Reader, appendData bo
return bytes, nil
}
// FileDriverFactory implements DriverFactory
type FileDriverFactory struct {
RootPath string
Perm
}
// NewDriver implements DriverFactory
func (factory *FileDriverFactory) NewDriver() (Driver, error) {
var err error
factory.RootPath, err = filepath.Abs(factory.RootPath)
if err != nil {
return nil, err
}
return &FileDriver{factory.RootPath, factory.Perm}, nil
}

View File

@@ -2,10 +2,11 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
package minio
import (
"errors"
"fmt"
"io"
"log"
"os"
@@ -13,19 +14,41 @@ import (
"time"
minio "github.com/minio/minio-go/v6"
"goftp.io/server/v2"
)
var (
_ Driver = &MinioDriver{}
_ server.Driver = &Driver{}
)
// MinioDriver implements Driver to store files in minio
type MinioDriver struct {
// Driver implements Driver to store files in minio
type Driver struct {
client *minio.Client
perm Perm
bucket string
}
// NewDriver implements DriverFactory
func NewDriver(endpoint, accessKeyID, secretAccessKey, location, bucket string, useSSL bool) (server.Driver, error) {
// Initialize minio client object.
minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL)
if err != nil {
return nil, err
}
if err = minioClient.MakeBucket(bucket, location); err != nil {
// Check to see if we already own this bucket (which happens if you run this twice)
exists, errBucketExists := minioClient.BucketExists(bucket)
if !exists || errBucketExists != nil {
return nil, err
}
}
return &Driver{
client: minioClient,
bucket: bucket,
}, nil
}
func buildMinioPath(p string) string {
return strings.TrimPrefix(p, "/")
}
@@ -38,26 +61,10 @@ func buildMinioDir(p string) string {
return v
}
type myPerm struct {
Perm
isDir bool
}
func (m *myPerm) GetMode(user string) (os.FileMode, error) {
mode, err := m.Perm.GetMode(user)
if err != nil {
return 0, err
}
if m.isDir {
return mode | os.ModeDir, nil
}
return mode, nil
}
type minioFileInfo struct {
p string
info minio.ObjectInfo
perm Perm
p string
info minio.ObjectInfo
isDir bool
}
func (m *minioFileInfo) Name() string {
@@ -69,8 +76,7 @@ func (m *minioFileInfo) Size() int64 {
}
func (m *minioFileInfo) Mode() os.FileMode {
mode, _ := m.perm.GetMode(m.p)
return mode
return os.ModePerm
}
func (m *minioFileInfo) ModTime() time.Time {
@@ -78,24 +84,14 @@ func (m *minioFileInfo) ModTime() time.Time {
}
func (m *minioFileInfo) IsDir() bool {
return m.Mode().IsDir()
return m.isDir
}
func (m *minioFileInfo) Sys() interface{} {
return nil
}
func (m *minioFileInfo) Owner() string {
owner, _ := m.perm.GetOwner(m.p)
return owner
}
func (m *minioFileInfo) Group() string {
group, _ := m.perm.GetGroup(m.p)
return group
}
func (driver *MinioDriver) isDir(path string) (bool, error) {
func (driver *Driver) isDir(path string) (bool, error) {
p := buildMinioDir(path)
info, err := driver.client.StatObject(driver.bucket, p, minio.StatObjectOptions{})
@@ -117,11 +113,11 @@ func (driver *MinioDriver) isDir(path string) (bool, error) {
}
// Stat implements Driver
func (driver *MinioDriver) Stat(path string) (FileInfo, error) {
func (driver *Driver) Stat(ctx *server.Context, path string) (os.FileInfo, error) {
if path == "/" {
return &minioFileInfo{
p: "/",
perm: &myPerm{driver.perm, true},
p: "/",
isDir: true,
}, nil
}
@@ -132,22 +128,22 @@ func (driver *MinioDriver) Stat(path string) (FileInfo, error) {
return nil, err
} else if isDir {
return &minioFileInfo{
p: path,
perm: &myPerm{driver.perm, true},
p: path,
isDir: true,
}, nil
}
return nil, errors.New("Not a directory")
}
isDir := strings.HasSuffix(objInfo.Key, "/")
return &minioFileInfo{
p: p,
info: objInfo,
perm: &myPerm{driver.perm, isDir},
p: p,
info: objInfo,
isDir: isDir,
}, nil
}
// ListDir implements Driver
func (driver *MinioDriver) ListDir(path string, callback func(FileInfo) error) error {
func (driver *Driver) ListDir(ctx *server.Context, path string, callback func(os.FileInfo) error) error {
doneCh := make(chan struct{})
defer close(doneCh)
@@ -168,9 +164,9 @@ func (driver *MinioDriver) ListDir(path string, callback func(FileInfo) error) e
isDir := strings.HasSuffix(object.Key, "/")
info := minioFileInfo{
p: strings.TrimPrefix(object.Key, p),
info: object,
perm: &myPerm{driver.perm, isDir},
p: strings.TrimPrefix(object.Key, p),
info: object,
isDir: isDir,
}
if err := callback(&info); err != nil {
@@ -182,7 +178,7 @@ func (driver *MinioDriver) ListDir(path string, callback func(FileInfo) error) e
}
// DeleteDir implements Driver
func (driver *MinioDriver) DeleteDir(path string) error {
func (driver *Driver) DeleteDir(ctx *server.Context, path string) error {
doneCh := make(chan struct{})
defer close(doneCh)
@@ -201,12 +197,12 @@ func (driver *MinioDriver) DeleteDir(path string) error {
}
// DeleteFile implements Driver
func (driver *MinioDriver) DeleteFile(path string) error {
func (driver *Driver) DeleteFile(ctx *server.Context, path string) error {
return driver.client.RemoveObject(driver.bucket, buildMinioPath(path))
}
// Rename implements Driver
func (driver *MinioDriver) Rename(fromPath string, toPath string) error {
func (driver *Driver) Rename(ctx *server.Context, fromPath string, toPath string) error {
src := minio.NewSourceInfo(driver.bucket, buildMinioPath(fromPath), nil)
dst, err := minio.NewDestinationInfo(driver.bucket, buildMinioPath(toPath), nil, nil)
if err != nil {
@@ -221,20 +217,28 @@ func (driver *MinioDriver) Rename(fromPath string, toPath string) error {
}
// MakeDir implements Driver
func (driver *MinioDriver) MakeDir(path string) error {
func (driver *Driver) MakeDir(ctx *server.Context, path string) error {
dirPath := buildMinioDir(path)
_, err := driver.client.PutObject(driver.bucket, dirPath, nil, 0, minio.PutObjectOptions{})
return err
}
// GetFile implements Driver
func (driver *MinioDriver) GetFile(path string, offset int64) (int64, io.ReadCloser, error) {
func (driver *Driver) GetFile(ctx *server.Context, path string, offset int64) (int64, io.ReadCloser, error) {
var opts = minio.GetObjectOptions{}
object, err := driver.client.GetObject(driver.bucket, buildMinioPath(path), opts)
if err != nil {
return 0, nil, err
}
object.Seek(offset, io.SeekStart)
defer func() {
if err != nil && object != nil {
object.Close()
}
}()
_, err = object.Seek(offset, io.SeekStart)
if err != nil {
return 0, nil, err
}
info, err := object.Stat()
if err != nil {
@@ -245,23 +249,27 @@ func (driver *MinioDriver) GetFile(path string, offset int64) (int64, io.ReadClo
}
// PutFile implements Driver
func (driver *MinioDriver) PutFile(destPath string, data io.Reader, appendData bool) (int64, error) {
func (driver *Driver) PutFile(ctx *server.Context, destPath string, data io.Reader, offset int64) (int64, error) {
p := buildMinioPath(destPath)
if !appendData {
if offset == -1 {
return driver.client.PutObject(driver.bucket, p, data, -1, minio.PutObjectOptions{ContentType: "application/octet-stream"})
}
tempFile := p + ".tmp"
//tempDstFile := p + ".dst"
defer func() {
if err := driver.DeleteFile(tempFile); err != nil {
if err := driver.DeleteFile(ctx, tempFile); err != nil {
log.Println(err)
}
/*if err := driver.DeleteFile(tempDstFile); err != nil {
log.Println(err)
}*/
}()
info, err := driver.client.StatObject(driver.bucket, p, minio.StatObjectOptions{})
if err != nil {
return 0, err
}
if offset != info.Size {
return 0, fmt.Errorf("It's unsupported that offset %d is not equal to %d", offset, info.Size)
}
size, err := driver.client.PutObject(driver.bucket, tempFile, data, -1, minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
return size, err
@@ -278,50 +286,3 @@ func (driver *MinioDriver) PutFile(destPath string, data io.Reader, appendData b
return size, driver.client.ComposeObject(dst, srcs)
}
// MinioDriverFactory implements DriverFactory
type MinioDriverFactory struct {
endpoint string
accessKeyID string
secretAccessKey string
useSSL bool
location string
bucket string
perm Perm
}
// NewMinioDriverFactory creates a DriverFactory implementation
func NewMinioDriverFactory(endpoint, accessKeyID, secretAccessKey, location, bucket string, useSSL bool, perm Perm) *MinioDriverFactory {
return &MinioDriverFactory{
endpoint: endpoint,
accessKeyID: accessKeyID,
secretAccessKey: secretAccessKey,
useSSL: useSSL,
location: location,
bucket: bucket,
perm: perm,
}
}
// NewDriver implements DriverFactory
func (factory *MinioDriverFactory) NewDriver() (Driver, error) {
// Initialize minio client object.
minioClient, err := minio.New(factory.endpoint, factory.accessKeyID, factory.secretAccessKey, factory.useSSL)
if err != nil {
return nil, err
}
if err = minioClient.MakeBucket(factory.bucket, factory.location); err != nil {
// Check to see if we already own this bucket (which happens if you run this twice)
exists, errBucketExists := minioClient.BucketExists(factory.bucket)
if !exists || errBucketExists != nil {
return nil, err
}
}
return &MinioDriver{
client: minioClient,
bucket: factory.bucket,
perm: factory.perm,
}, nil
}

View File

@@ -6,14 +6,6 @@ package server
import "os"
// FileInfo represents an file interface
type FileInfo interface {
os.FileInfo
Owner() string
Group() string
}
type fileInfo struct {
os.FileInfo

View File

@@ -1,4 +1,4 @@
module goftp.io/server
module goftp.io/server/v2
go 1.12

View File

View File

@@ -28,10 +28,10 @@ func (formatter listFormatter) Short() []byte {
func (formatter listFormatter) Detailed() []byte {
var buf bytes.Buffer
for _, file := range formatter {
fmt.Fprintf(&buf, file.Mode().String())
fmt.Fprint(&buf, file.Mode().String())
fmt.Fprintf(&buf, " 1 %s %s ", file.Owner(), file.Group())
fmt.Fprintf(&buf, lpad(strconv.FormatInt(file.Size(), 10), 12))
fmt.Fprintf(&buf, file.ModTime().Format(" Jan _2 15:04 "))
fmt.Fprint(&buf, lpad(strconv.FormatInt(file.Size(), 10), 12))
fmt.Fprint(&buf, file.ModTime().Format(" Jan _2 15:04 "))
fmt.Fprintf(&buf, "%s\r\n", file.Name())
}
return buf.Bytes()

View File

176
vendor/goftp.io/server/v2/notifier.go generated vendored Normal file
View File

@@ -0,0 +1,176 @@
// Copyright 2020 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
// Notifier represents a notification operator interface
type Notifier interface {
BeforeLoginUser(ctx *Context, userName string)
BeforePutFile(ctx *Context, dstPath string)
BeforeDeleteFile(ctx *Context, dstPath string)
BeforeChangeCurDir(ctx *Context, oldCurDir, newCurDir string)
BeforeCreateDir(ctx *Context, dstPath string)
BeforeDeleteDir(ctx *Context, dstPath string)
BeforeDownloadFile(ctx *Context, dstPath string)
AfterUserLogin(ctx *Context, userName, password string, passMatched bool, err error)
AfterFilePut(ctx *Context, dstPath string, size int64, err error)
AfterFileDeleted(ctx *Context, dstPath string, err error)
AfterFileDownloaded(ctx *Context, dstPath string, size int64, err error)
AfterCurDirChanged(ctx *Context, oldCurDir, newCurDir string, err error)
AfterDirCreated(ctx *Context, dstPath string, err error)
AfterDirDeleted(ctx *Context, dstPath string, err error)
}
type notifierList []Notifier
var (
_ Notifier = notifierList{}
)
func (notifiers notifierList) BeforeLoginUser(ctx *Context, userName string) {
for _, notifier := range notifiers {
notifier.BeforeLoginUser(ctx, userName)
}
}
func (notifiers notifierList) BeforePutFile(ctx *Context, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforePutFile(ctx, dstPath)
}
}
func (notifiers notifierList) BeforeDeleteFile(ctx *Context, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeDeleteFile(ctx, dstPath)
}
}
func (notifiers notifierList) BeforeChangeCurDir(ctx *Context, oldCurDir, newCurDir string) {
for _, notifier := range notifiers {
notifier.BeforeChangeCurDir(ctx, oldCurDir, newCurDir)
}
}
func (notifiers notifierList) BeforeCreateDir(ctx *Context, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeCreateDir(ctx, dstPath)
}
}
func (notifiers notifierList) BeforeDeleteDir(ctx *Context, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeDeleteDir(ctx, dstPath)
}
}
func (notifiers notifierList) BeforeDownloadFile(ctx *Context, dstPath string) {
for _, notifier := range notifiers {
notifier.BeforeDownloadFile(ctx, dstPath)
}
}
func (notifiers notifierList) AfterUserLogin(ctx *Context, userName, password string, passMatched bool, err error) {
for _, notifier := range notifiers {
notifier.AfterUserLogin(ctx, userName, password, passMatched, err)
}
}
func (notifiers notifierList) AfterFilePut(ctx *Context, dstPath string, size int64, err error) {
for _, notifier := range notifiers {
notifier.AfterFilePut(ctx, dstPath, size, err)
}
}
func (notifiers notifierList) AfterFileDeleted(ctx *Context, dstPath string, err error) {
for _, notifier := range notifiers {
notifier.AfterFileDeleted(ctx, dstPath, err)
}
}
func (notifiers notifierList) AfterFileDownloaded(ctx *Context, dstPath string, size int64, err error) {
for _, notifier := range notifiers {
notifier.AfterFileDownloaded(ctx, dstPath, size, err)
}
}
func (notifiers notifierList) AfterCurDirChanged(ctx *Context, oldCurDir, newCurDir string, err error) {
for _, notifier := range notifiers {
notifier.AfterCurDirChanged(ctx, oldCurDir, newCurDir, err)
}
}
func (notifiers notifierList) AfterDirCreated(ctx *Context, dstPath string, err error) {
for _, notifier := range notifiers {
notifier.AfterDirCreated(ctx, dstPath, err)
}
}
func (notifiers notifierList) AfterDirDeleted(ctx *Context, dstPath string, err error) {
for _, notifier := range notifiers {
notifier.AfterDirDeleted(ctx, dstPath, err)
}
}
// NullNotifier implements Notifier
type NullNotifier struct{}
var (
_ Notifier = &NullNotifier{}
)
// BeforeLoginUser implements Notifier
func (NullNotifier) BeforeLoginUser(ctx *Context, userName string) {
}
// BeforePutFile implements Notifier
func (NullNotifier) BeforePutFile(ctx *Context, dstPath string) {
}
// BeforeDeleteFile implements Notifier
func (NullNotifier) BeforeDeleteFile(ctx *Context, dstPath string) {
}
// BeforeChangeCurDir implements Notifier
func (NullNotifier) BeforeChangeCurDir(ctx *Context, oldCurDir, newCurDir string) {
}
// BeforeCreateDir implements Notifier
func (NullNotifier) BeforeCreateDir(ctx *Context, dstPath string) {
}
// BeforeDeleteDir implements Notifier
func (NullNotifier) BeforeDeleteDir(ctx *Context, dstPath string) {
}
// BeforeDownloadFile implements Notifier
func (NullNotifier) BeforeDownloadFile(ctx *Context, dstPath string) {
}
// AfterUserLogin implements Notifier
func (NullNotifier) AfterUserLogin(ctx *Context, userName, password string, passMatched bool, err error) {
}
// AfterFilePut implements Notifier
func (NullNotifier) AfterFilePut(ctx *Context, dstPath string, size int64, err error) {
}
// AfterFileDeleted implements Notifier
func (NullNotifier) AfterFileDeleted(ctx *Context, dstPath string, err error) {
}
// AfterFileDownloaded implements Notifier
func (NullNotifier) AfterFileDownloaded(ctx *Context, dstPath string, size int64, err error) {
}
// AfterCurDirChanged implements Notifier
func (NullNotifier) AfterCurDirChanged(ctx *Context, oldCurDir, newCurDir string, err error) {
}
// AfterDirCreated implements Notifier
func (NullNotifier) AfterDirCreated(ctx *Context, dstPath string, err error) {
}
// AfterDirDeleted implements Notifier
func (NullNotifier) AfterDirDeleted(ctx *Context, dstPath string, err error) {
}

View File

38
vendor/goftp.io/server/v2/ratelimit/limiter.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2020 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package ratelimit
import (
"time"
)
// Limiter represents a rate limiter
type Limiter struct {
rate time.Duration
count int64
t time.Time
}
// New create a limiter for transfer speed, parameter rate means bytes per second
// 0 means don't limit
func New(rate int64) *Limiter {
return &Limiter{
rate: time.Duration(rate),
count: 0,
t: time.Now(),
}
}
// Wait sleep when write count bytes
func (l *Limiter) Wait(count int) {
if l.rate == 0 {
return
}
l.count += int64(count)
t := time.Duration(l.count)*time.Second/l.rate - time.Since(l.t)
if t > 0 {
time.Sleep(t)
}
}

27
vendor/goftp.io/server/v2/ratelimit/reader.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2020 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package ratelimit
import "io"
type reader struct {
r io.Reader
l *Limiter
}
// Read Read
func (r *reader) Read(buf []byte) (int, error) {
n, err := r.r.Read(buf)
r.l.Wait(n)
return n, err
}
// Reader returns a reader with limiter
func Reader(r io.Reader, l *Limiter) io.Reader {
return &reader{
r: r,
l: l,
}
}

26
vendor/goftp.io/server/v2/ratelimit/writer.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2020 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package ratelimit
import "io"
type writer struct {
w io.Writer
l *Limiter
}
// Write Write
func (w *writer) Write(buf []byte) (int, error) {
w.l.Wait(len(buf))
return w.w.Write(buf)
}
// Writer returns a writer with limiter
func Writer(w io.Writer, l *Limiter) io.Writer {
return &writer{
w: w,
l: l,
}
}

View File

@@ -12,21 +12,29 @@ import (
"fmt"
"net"
"strconv"
"goftp.io/server/v2/ratelimit"
)
// Version returns the library version
func Version() string {
return "0.3.1"
}
var (
version = "2.0beta"
)
// ServerOpts contains parameters for server.NewServer()
type ServerOpts struct {
// The factory that will be used to create a new FTPDriver instance for
// each client connection. This is a mandatory option.
Factory DriverFactory
// Options contains parameters for server.NewServer()
type Options struct {
// This server supported commands, if blank, it will be defaultCommands
// So that users could override the Commands
Commands map[string]Command
// The driver that will be used to handle files persistent
Driver Driver
// How to hanle the authenticate requests
Auth Auth
// How to handle the perm controls
Perm Perm
// Server Name, Default is Go Ftp Server
Name string
@@ -56,10 +64,16 @@ type ServerOpts struct {
// If ture TLS is used in RFC4217 mode
ExplicitFTPS bool
// If true, client must upgrade to TLS before sending any other command
ForceTLS bool
WelcomeMessage string
// A logger implementation, if nil the StdLogger is used
Logger Logger
// Rate Limit per connection bytes per second, 0 means no limit
RateLimit int64
}
// Server is the root of your FTP application. You should instantiate one
@@ -67,7 +81,7 @@ type ServerOpts struct {
//
// Always use the NewServer() method to create a new Server.
type Server struct {
*ServerOpts
*Options
listenTo string
logger Logger
listener net.Listener
@@ -76,18 +90,20 @@ type Server struct {
cancel context.CancelFunc
feats string
notifiers notifierList
// rate limiter per connection
rateLimiter *ratelimit.Limiter
}
// ErrServerClosed is returned by ListenAndServe() or Serve() when a shutdown
// was requested.
var ErrServerClosed = errors.New("ftp: Server closed")
// serverOptsWithDefaults copies an ServerOpts struct into a new struct,
// optsWithDefaults copies an Options struct into a new struct,
// then adds any default values that are missing and returns the new data.
func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
var newOpts ServerOpts
func optsWithDefaults(opts *Options) *Options {
var newOpts Options
if opts == nil {
opts = &ServerOpts{}
opts = &Options{}
}
if opts.Hostname == "" {
newOpts.Hostname = "::"
@@ -95,11 +111,11 @@ func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
newOpts.Hostname = opts.Hostname
}
if opts.Port == 0 {
newOpts.Port = 3000
newOpts.Port = 2121
} else {
newOpts.Port = opts.Port
}
newOpts.Factory = opts.Factory
newOpts.Driver = opts.Driver
if opts.Name == "" {
newOpts.Name = "Go FTP Server"
} else {
@@ -122,6 +138,11 @@ func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
newOpts.Logger = &StdLogger{}
}
if opts.Commands == nil {
newOpts.Commands = defaultCommands
}
newOpts.Perm = opts.Perm
newOpts.TLS = opts.TLS
newOpts.KeyFile = opts.KeyFile
newOpts.CertFile = opts.CertFile
@@ -129,40 +150,53 @@ func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
newOpts.PublicIP = opts.PublicIP
newOpts.PassivePorts = opts.PassivePorts
newOpts.RateLimit = opts.RateLimit
return &newOpts
}
// NewServer initialises a new FTP server. Configuration options are provided
// via an instance of ServerOpts. Calling this function in your code will
// via an instance of Options. Calling this function in your code will
// probably look something like this:
//
// factory := &MyDriverFactory{}
// server := server.NewServer(&server.ServerOpts{ Factory: factory })
//
// or:
//
// factory := &MyDriverFactory{}
// opts := &server.ServerOpts{
// Factory: factory,
// driver := &MyDriver{}
// opts := &server.Options{
// Driver: driver,
// Auth: auth,
// Port: 2000,
// Perm: perm,
// Hostname: "127.0.0.1",
// }
// server := server.NewServer(opts)
// server, err := server.NewServer(opts)
//
func NewServer(opts *ServerOpts) *Server {
opts = serverOptsWithDefaults(opts)
func NewServer(opts *Options) (*Server, error) {
opts = optsWithDefaults(opts)
if opts.Perm == nil {
return nil, errors.New("No perm implementation")
}
s := new(Server)
s.ServerOpts = opts
s.Options = opts
s.listenTo = net.JoinHostPort(opts.Hostname, strconv.Itoa(opts.Port))
s.logger = opts.Logger
var curFeats = featCmds
if opts.TLS {
curFeats += " AUTH TLS\n PBSZ\n PROT\n"
}
s.feats = fmt.Sprintf(feats, curFeats)
return s
var (
feats = "Extensions supported:\n%s"
featCmds = " UTF8\n"
)
for k, v := range s.Commands {
if v.IsExtend() {
featCmds = featCmds + " " + k + "\n"
}
}
if opts.TLS {
featCmds += " AUTH TLS\n PBSZ\n PROT\n"
}
s.feats = fmt.Sprintf(feats, featCmds)
s.rateLimiter = ratelimit.New(opts.RateLimit)
return s, nil
}
// RegisterNotifer registers a notifier
@@ -174,19 +208,22 @@ func (server *Server) RegisterNotifer(notifier Notifier) {
// an active net.TCPConn. The TCP connection should already be open before
// it is handed to this functions. driver is an instance of FTPDriver that
// will handle all auth and persistence details.
func (server *Server) newConn(tcpConn net.Conn, driver Driver) *Conn {
c := new(Conn)
c.curDir = "/"
c.conn = tcpConn
c.controlReader = bufio.NewReader(tcpConn)
c.controlWriter = bufio.NewWriter(tcpConn)
c.driver = driver
c.auth = server.Auth
c.server = server
c.sessionID = newSessionID()
c.logger = server.logger
c.tlsConfig = server.tlsConfig
return c
func (server *Server) newSession(id string, tcpConn net.Conn) *Session {
return &Session{
id: id,
server: server,
conn: tcpConn,
controlReader: bufio.NewReader(tcpConn),
controlWriter: bufio.NewWriter(tcpConn),
curDir: "/",
reqUser: "",
user: "",
renameFrom: "",
lastFilePos: -1,
closed: false,
tls: false,
Data: make(map[string]interface{}),
}
}
func simpleTLSConfig(certFile, keyFile string) (*tls.Config, error) {
@@ -216,13 +253,13 @@ func (server *Server) ListenAndServe() error {
var listener net.Listener
var err error
if server.ServerOpts.TLS {
if server.Options.TLS {
server.tlsConfig, err = simpleTLSConfig(server.CertFile, server.KeyFile)
if err != nil {
return err
}
if server.ServerOpts.ExplicitFTPS {
if server.Options.ExplicitFTPS {
listener, err = net.Listen("tcp", server.listenTo)
} else {
listener, err = tls.Listen("tcp", server.listenTo, server.tlsConfig)
@@ -234,8 +271,7 @@ func (server *Server) ListenAndServe() error {
return err
}
sessionID := ""
server.logger.Printf(sessionID, "%s listening on %d", server.Name, server.Port)
server.logger.Printf("", "%s listening on %d", server.Name, server.Port)
return server.Serve(listener)
}
@@ -246,7 +282,8 @@ func (server *Server) ListenAndServe() error {
func (server *Server) Serve(l net.Listener) error {
server.listener = l
server.ctx, server.cancel = context.WithCancel(context.Background())
sessionID := ""
defer server.cancel()
sessionID := newSessionID()
for {
tcpConn, err := server.listener.Accept()
if err != nil {
@@ -261,14 +298,9 @@ func (server *Server) Serve(l net.Listener) error {
}
return err
}
driver, err := server.Factory.NewDriver()
if err != nil {
server.logger.Printf(sessionID, "Error creating driver, aborting client connection: %v", err)
tcpConn.Close()
} else {
ftpConn := server.newConn(tcpConn, driver)
go ftpConn.Serve()
}
ftpConn := server.newSession(sessionID, tcpConn)
go ftpConn.Serve()
}
}

View File

@@ -26,54 +26,57 @@ const (
defaultWelcomeMessage = "Welcome to the Go FTP Server"
)
// Conn represents a connection between ftp client and the server
type Conn struct {
// Session represents a session between ftp client and the server
type Session struct {
conn net.Conn
controlReader *bufio.Reader
controlWriter *bufio.Writer
dataConn DataSocket
driver Driver
auth Auth
logger Logger
server *Server
tlsConfig *tls.Config
sessionID string
id string
curDir string
reqUser string
user string
renameFrom string
lastFilePos int64
appendData bool
preCommand string
closed bool
tls bool
clientSoft string
Data map[string]interface{} // shared data between different commands
}
// RemoteAddr returns the remote ftp client's address
func (conn *Conn) RemoteAddr() net.Addr {
return conn.conn.RemoteAddr()
func (sess *Session) RemoteAddr() net.Addr {
return sess.conn.RemoteAddr()
}
// LoginUser returns the login user name if login
func (conn *Conn) LoginUser() string {
return conn.user
func (sess *Session) LoginUser() string {
return sess.user
}
// IsLogin returns if user has login
func (conn *Conn) IsLogin() bool {
return len(conn.user) > 0
func (sess *Session) IsLogin() bool {
return len(sess.user) > 0
}
// PublicIP returns the public ip of the server
func (conn *Conn) PublicIP() string {
return conn.server.PublicIP
func (sess *Session) PublicIP() string {
return sess.server.PublicIP
}
func (conn *Conn) passiveListenIP() string {
// Options returns the server options
func (sess *Session) Options() *Options {
return sess.server.Options
}
func (sess *Session) passiveListenIP() string {
var listenIP string
if len(conn.PublicIP()) > 0 {
listenIP = conn.PublicIP()
if len(sess.PublicIP()) > 0 {
listenIP = sess.PublicIP()
} else {
listenIP = conn.conn.LocalAddr().(*net.TCPAddr).IP.String()
listenIP = sess.conn.LocalAddr().(*net.TCPAddr).IP.String()
}
if listenIP == "::1" {
@@ -88,9 +91,9 @@ func (conn *Conn) passiveListenIP() string {
}
// PassivePort returns the port which could be used by passive mode.
func (conn *Conn) PassivePort() int {
if len(conn.server.PassivePorts) > 0 {
portRange := strings.Split(conn.server.PassivePorts, "-")
func (sess *Session) PassivePort() int {
if len(sess.server.PassivePorts) > 0 {
portRange := strings.Split(sess.server.PassivePorts, "-")
if len(portRange) != 2 {
log.Println("empty port")
@@ -123,59 +126,59 @@ func newSessionID() string {
// message when the connection closes. This loop will be running inside a
// goroutine, so use this channel to be notified when the connection can be
// cleaned up.
func (conn *Conn) Serve() {
conn.logger.Print(conn.sessionID, "Connection Established")
func (sess *Session) Serve() {
sess.log("Connection Established")
// send welcome
conn.writeMessage(220, conn.server.WelcomeMessage)
sess.writeMessage(220, sess.server.WelcomeMessage)
// read commands
for {
line, err := conn.controlReader.ReadString('\n')
line, err := sess.controlReader.ReadString('\n')
if err != nil {
if err != io.EOF {
conn.logger.Print(conn.sessionID, fmt.Sprint("read error:", err))
sess.log(fmt.Sprint("read error:", err))
}
break
}
conn.receiveLine(line)
sess.receiveLine(line)
// QUIT command closes connection, break to avoid error on reading from
// closed socket
if conn.closed == true {
if sess.closed {
break
}
}
conn.Close()
conn.logger.Print(conn.sessionID, "Connection Terminated")
sess.Close()
sess.log("Connection Terminated")
}
// Close will manually close this connection, even if the client isn't ready.
func (conn *Conn) Close() {
conn.conn.Close()
conn.closed = true
conn.reqUser = ""
conn.user = ""
if conn.dataConn != nil {
conn.dataConn.Close()
conn.dataConn = nil
func (sess *Session) Close() {
sess.conn.Close()
sess.closed = true
sess.reqUser = ""
sess.user = ""
if sess.dataConn != nil {
sess.dataConn.Close()
sess.dataConn = nil
}
}
func (conn *Conn) upgradeToTLS() error {
conn.logger.Print(conn.sessionID, "Upgrading connectiion to TLS")
tlsConn := tls.Server(conn.conn, conn.tlsConfig)
func (sess *Session) upgradeToTLS() error {
sess.log("Upgrading connectiion to TLS")
tlsConn := tls.Server(sess.conn, sess.server.tlsConfig)
err := tlsConn.Handshake()
if err == nil {
conn.conn = tlsConn
conn.controlReader = bufio.NewReader(tlsConn)
conn.controlWriter = bufio.NewWriter(tlsConn)
conn.tls = true
sess.conn = tlsConn
sess.controlReader = bufio.NewReader(tlsConn)
sess.controlWriter = bufio.NewWriter(tlsConn)
sess.tls = true
}
return err
}
// receiveLine accepts a single line FTP command and co-ordinates an
// appropriate response.
func (conn *Conn) receiveLine(line string) {
func (sess *Session) receiveLine(line string) {
defer func() {
if e := recover(); e != nil {
var buf bytes.Buffer
@@ -191,50 +194,56 @@ func (conn *Conn) receiveLine(line string) {
fmt.Fprintf(&buf, "%v:%v", file, line)
}
conn.logger.Print(conn.sessionID, buf.String())
sess.log(buf.String())
}
}()
command, param := conn.parseLine(line)
conn.logger.PrintCommand(conn.sessionID, command, param)
cmdObj := commands[strings.ToUpper(command)]
command, param := sess.parseLine(line)
sess.server.Logger.PrintCommand(sess.id, command, param)
var (
commands = sess.server.Commands
theCmd = strings.ToUpper(command)
cmdObj = commands[theCmd]
)
if cmdObj == nil {
conn.writeMessage(500, "Command not found")
sess.writeMessage(500, "Command not found")
return
}
if cmdObj.RequireParam() && param == "" {
conn.writeMessage(553, "action aborted, required param missing")
} else if cmdObj.RequireAuth() && conn.user == "" {
conn.writeMessage(530, "not logged in")
sess.writeMessage(553, "action aborted, required param missing")
} else if sess.server.Options.ForceTLS && !sess.tls && !(cmdObj == commands["AUTH"] && param == "TLS") {
sess.writeMessage(534, "Request denied for policy reasons. AUTH TLS required.")
} else if cmdObj.RequireAuth() && sess.user == "" {
sess.writeMessage(530, "not logged in")
} else {
cmdObj.Execute(conn, param)
cmdObj.Execute(sess, param)
sess.preCommand = theCmd
}
}
func (conn *Conn) parseLine(line string) (string, string) {
func (sess *Session) parseLine(line string) (string, string) {
params := strings.SplitN(strings.Trim(line, "\r\n"), " ", 2)
if len(params) == 1 {
return params[0], ""
}
return params[0], strings.TrimSpace(params[1])
return params[0], params[1]
}
// writeMessage will send a standard FTP response back to the client.
func (conn *Conn) writeMessage(code int, message string) (wrote int, err error) {
conn.logger.PrintResponse(conn.sessionID, code, message)
func (sess *Session) writeMessage(code int, message string) {
sess.server.Logger.PrintResponse(sess.id, code, message)
line := fmt.Sprintf("%d %s\r\n", code, message)
wrote, err = conn.controlWriter.WriteString(line)
conn.controlWriter.Flush()
return
_, _ = sess.controlWriter.WriteString(line)
sess.controlWriter.Flush()
}
// writeMessage will send a standard FTP response back to the client.
func (conn *Conn) writeMessageMultiline(code int, message string) (wrote int, err error) {
conn.logger.PrintResponse(conn.sessionID, code, message)
func (sess *Session) writeMessageMultiline(code int, message string) {
sess.server.Logger.PrintResponse(sess.id, code, message)
line := fmt.Sprintf("%d-%s\r\n%d END\r\n", code, message, code)
wrote, err = conn.controlWriter.WriteString(line)
conn.controlWriter.Flush()
return
_, _ = sess.controlWriter.WriteString(line)
sess.controlWriter.Flush()
}
// buildPath takes a client supplied path or filename and generates a safe
@@ -254,13 +263,13 @@ func (conn *Conn) writeMessageMultiline(code int, message string) (wrote int, er
// The driver implementation is responsible for deciding how to treat this path.
// Obviously they MUST NOT just read the path off disk. The probably want to
// prefix the path with something to scope the users access to a sandbox.
func (conn *Conn) buildPath(filename string) (fullPath string) {
func (sess *Session) buildPath(filename string) (fullPath string) {
if len(filename) > 0 && filename[0:1] == "/" {
fullPath = filepath.Clean(filename)
} else if len(filename) > 0 && filename != "-a" {
fullPath = filepath.Clean(conn.curDir + "/" + filename)
fullPath = filepath.Clean(sess.curDir + "/" + filename)
} else {
fullPath = filepath.Clean(conn.curDir)
fullPath = filepath.Clean(sess.curDir)
}
fullPath = strings.Replace(fullPath, "//", "/", -1)
fullPath = strings.Replace(fullPath, string(filepath.Separator), "/", -1)
@@ -269,34 +278,41 @@ func (conn *Conn) buildPath(filename string) (fullPath string) {
// sendOutofbandData will send a string to the client via the currently open
// data socket. Assumes the socket is open and ready to be used.
func (conn *Conn) sendOutofbandData(data []byte) {
func (sess *Session) sendOutofbandData(data []byte) {
bytes := len(data)
if conn.dataConn != nil {
conn.dataConn.Write(data)
conn.dataConn.Close()
conn.dataConn = nil
if sess.dataConn != nil {
_, _ = sess.dataConn.Write(data)
sess.dataConn.Close()
sess.dataConn = nil
}
message := "Closing data connection, sent " + strconv.Itoa(bytes) + " bytes"
conn.writeMessage(226, message)
sess.writeMessage(226, message)
}
func (conn *Conn) sendOutofBandDataWriter(data io.ReadCloser) error {
conn.lastFilePos = 0
bytes, err := io.Copy(conn.dataConn, data)
func (sess *Session) sendOutofBandDataWriter(data io.ReadCloser) error {
bytes, err := io.Copy(sess.dataConn, data)
if err != nil {
conn.dataConn.Close()
conn.dataConn = nil
sess.dataConn.Close()
sess.dataConn = nil
return err
}
message := "Closing data connection, sent " + strconv.Itoa(int(bytes)) + " bytes"
conn.writeMessage(226, message)
conn.dataConn.Close()
conn.dataConn = nil
sess.writeMessage(226, message)
sess.dataConn.Close()
sess.dataConn = nil
return nil
}
func (conn *Conn) changeCurDir(path string) error {
conn.curDir = path
func (sess *Session) changeCurDir(path string) error {
sess.curDir = path
return nil
}
func (sess *Session) log(message interface{}) {
sess.server.logger.Print(sess.id, message)
}
func (sess *Session) logf(format string, v ...interface{}) {
sess.server.logger.Printf(sess.id, format, v...)
}

23
vendor/modules.txt vendored
View File

@@ -1,9 +1,5 @@
# gitea.com/goftp/leveldb-auth v0.0.0-20190711092309-e8e3d5ad5ac8
gitea.com/goftp/leveldb-auth
# gitea.com/goftp/leveldb-perm v0.0.0-20190711092750-00b79e6da99c
gitea.com/goftp/leveldb-perm
# gitea.com/goftp/qiniu-driver v0.0.0-20191027083326-6e505f23c4f0
gitea.com/goftp/qiniu-driver
# gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e
gitea.com/lunny/log
# gitea.com/lunny/tango v0.6.1
@@ -37,18 +33,6 @@ github.com/minio/sha256-simd
github.com/mitchellh/go-homedir
# github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
github.com/oxtoacart/bpool
# github.com/qiniu/api.v6 v6.0.9+incompatible
github.com/qiniu/api.v6/auth/digest
github.com/qiniu/api.v6/conf
github.com/qiniu/api.v6/io
github.com/qiniu/api.v6/rs
github.com/qiniu/api.v6/rsf
github.com/qiniu/api.v6/url
# github.com/qiniu/bytes v0.0.0-20140728010635-4887e7b2bde3
github.com/qiniu/bytes
github.com/qiniu/bytes/seekable
# github.com/qiniu/rpc v0.0.0-20140728010754-30c22466d920
github.com/qiniu/rpc
# github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/httpfs/vfsutil
# github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
@@ -70,8 +54,11 @@ github.com/syndtr/goleveldb/leveldb/util
github.com/unknwon/com
# github.com/unknwon/goconfig v0.0.0-20190425194916-3dba17dd7b9e
github.com/unknwon/goconfig
# goftp.io/server v0.3.5-0.20200428022247-5cd49dc54bdb
goftp.io/server
# goftp.io/server/v2 v2.0.0
goftp.io/server/v2
goftp.io/server/v2/driver/file
goftp.io/server/v2/driver/minio
goftp.io/server/v2/ratelimit
# golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/crypto/argon2
golang.org/x/crypto/blake2b

View File

@@ -10,6 +10,8 @@ import (
"gitea.com/tango/renders"
"gitea.com/tango/session"
"gitea.com/tango/xsrf"
"goftp.io/server/v2"
)
var _ auther = new(BaseAction)
@@ -150,12 +152,8 @@ func (d *DownAction) Get() error {
return err
}
driver, err := Factory.NewDriver()
if err != nil {
return err
}
_, rd, err := driver.GetFile(p, 0)
_, rd, err := Driver.GetFile(&server.Context{
}, p, 0)
if err != nil {
return err
}

View File

@@ -6,7 +6,7 @@ import (
"path"
"gitea.com/tango/renders"
"goftp.io/server"
"goftp.io/server/v2"
)
func hasPerm(mode os.FileMode, idx int, rOrW string) bool {
@@ -39,12 +39,9 @@ func (c *PermAction) Get() error {
parent = "/"
p = "/"
}
driver, err := Factory.NewDriver()
if err != nil {
return err
}
var pathinfos []server.FileInfo
err = driver.ListDir(p, func(f server.FileInfo) error {
var pathinfos []os.FileInfo
err = Driver.ListDir(&server.Context{
}, p, func(f os.FileInfo) error {
if f.Name() != "." {
pathinfos = append(pathinfos, f)
}

View File

@@ -13,7 +13,7 @@ import (
"gitea.com/tango/session"
"gitea.com/tango/xsrf"
"github.com/syndtr/goleveldb/leveldb"
"goftp.io/server"
"goftp.io/server/v2"
)
const (
@@ -26,7 +26,7 @@ const (
var (
DB UserDB
Perm server.Perm
Factory server.DriverFactory
Driver server.Driver
adminUser string
)