parent
30c963a5f0
commit
61a1c93f0f
89 changed files with 15859 additions and 2 deletions
105
server/apis/auth.go
Normal file
105
server/apis/auth.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/httpworker"
|
||||
)
|
||||
|
||||
func (srv *SrvShare) LoginHandler(res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
srv.Http.Fill(httputil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
act := req.FormValue(srv.Conf.KeyAct)
|
||||
todo := func(res http.ResponseWriter, req *http.Request) interface{} { return httputil.Err404 }
|
||||
switch act {
|
||||
case srv.Conf.ActLogin:
|
||||
todo = srv.Login
|
||||
case srv.Conf.ActLogout:
|
||||
todo = srv.Logout
|
||||
default:
|
||||
srv.Http.Fill(httputil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&httpworker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(todo),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httputil.Err503, res)
|
||||
return
|
||||
}
|
||||
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httputil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) Login(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
// all users need to pass same wall to login
|
||||
if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) ||
|
||||
!srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdLogin) {
|
||||
return httputil.Err504
|
||||
}
|
||||
|
||||
return srv.login(
|
||||
req.FormValue(srv.Conf.KeyAdminId),
|
||||
req.FormValue(srv.Conf.KeyAdminPwd),
|
||||
res,
|
||||
)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) login(adminId string, adminPwd string, res http.ResponseWriter) interface{} {
|
||||
if adminId != srv.Conf.AdminId ||
|
||||
adminPwd != srv.Conf.AdminPwd {
|
||||
return httputil.Err401
|
||||
}
|
||||
|
||||
token := srv.Walls.MakeLoginToken(srv.Conf.AdminId)
|
||||
if token == "" {
|
||||
return httputil.Err500
|
||||
}
|
||||
|
||||
srv.Http.SetCookie(res, srv.Conf.KeyToken, token)
|
||||
return httputil.Ok200
|
||||
}
|
||||
|
||||
func (srv *SrvShare) Logout(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
srv.Http.SetCookie(res, srv.Conf.KeyToken, "-")
|
||||
return httputil.Ok200
|
||||
}
|
||||
|
||||
func (srv *SrvShare) IsValidLength(length int64) bool {
|
||||
return length > 0 && length <= srv.Conf.MaxUpBytesPerSec
|
||||
}
|
||||
|
||||
func (srv *SrvShare) IsValidStart(start, expectStart int64) bool {
|
||||
return start == expectStart
|
||||
}
|
||||
|
||||
func (srv *SrvShare) IsValidShareId(shareId string) bool {
|
||||
// id could be 0 for dev environment
|
||||
if srv.Conf.Production {
|
||||
return len(shareId) == 64
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (srv *SrvShare) IsValidDownLimit(limit int) bool {
|
||||
return limit >= -1
|
||||
}
|
||||
|
||||
func IsValidFileName(fileName string) bool {
|
||||
return fileName != "" && len(fileName) < 240
|
||||
}
|
78
server/apis/auth_test.go
Normal file
78
server/apis/auth_test.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/cfg"
|
||||
"quickshare/server/libs/encrypt"
|
||||
"quickshare/server/libs/httputil"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
|
||||
type testCase struct {
|
||||
Desc string
|
||||
AdminId string
|
||||
AdminPwd string
|
||||
Result interface{}
|
||||
VerifyToken bool
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
testCase{
|
||||
Desc: "invalid input",
|
||||
AdminId: "",
|
||||
AdminPwd: "",
|
||||
Result: httputil.Err401,
|
||||
VerifyToken: false,
|
||||
},
|
||||
testCase{
|
||||
Desc: "account not match",
|
||||
AdminId: "unknown",
|
||||
AdminPwd: "unknown",
|
||||
Result: httputil.Err401,
|
||||
VerifyToken: false,
|
||||
},
|
||||
testCase{
|
||||
Desc: "succeed to login",
|
||||
AdminId: conf.AdminId,
|
||||
AdminPwd: conf.AdminPwd,
|
||||
Result: httputil.Ok200,
|
||||
VerifyToken: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := NewSrvShare(conf)
|
||||
res := &stubWriter{Headers: map[string][]string{}}
|
||||
ret := srv.login(testCase.AdminId, testCase.AdminPwd, res)
|
||||
|
||||
if ret != testCase.Result {
|
||||
t.Fatalf("login: reponse=%v testCase=%v", ret, testCase.Result)
|
||||
}
|
||||
|
||||
// verify cookie (only token.adminid part))
|
||||
if testCase.VerifyToken {
|
||||
cookieVal := strings.Replace(
|
||||
res.Header().Get("Set-Cookie"),
|
||||
fmt.Sprintf("%s=", conf.KeyToken),
|
||||
"",
|
||||
1,
|
||||
)
|
||||
|
||||
gotTokenStr := strings.Split(cookieVal, ";")[0]
|
||||
token := encrypt.JwtEncrypterMaker(conf.SecretKey)
|
||||
token.FromStr(gotTokenStr)
|
||||
gotToken, found := token.Get(conf.KeyAdminId)
|
||||
if !found || conf.AdminId != gotToken {
|
||||
t.Fatalf("login: token admin id unmatch got=%v expect=%v", gotToken, conf.AdminId)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
66
server/apis/client.go
Normal file
66
server/apis/client.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/httpworker"
|
||||
)
|
||||
|
||||
func (srv *SrvShare) ClientHandler(res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
srv.Http.Fill(httputil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&httpworker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(srv.GetClient),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httputil.Err503, res)
|
||||
return
|
||||
}
|
||||
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httputil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) GetClient(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) {
|
||||
return httputil.Err504
|
||||
}
|
||||
|
||||
return srv.getClient(res, req, req.URL.EscapedPath())
|
||||
}
|
||||
|
||||
func (srv *SrvShare) getClient(res http.ResponseWriter, req *http.Request, relPath string) interface{} {
|
||||
if strings.HasSuffix(relPath, "/") {
|
||||
relPath = relPath + "index.html"
|
||||
}
|
||||
if !IsValidClientPath(relPath) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
fullPath := filepath.Clean(filepath.Join("./public", relPath))
|
||||
http.ServeFile(res, req, fullPath)
|
||||
return 0
|
||||
}
|
||||
|
||||
func IsValidClientPath(fullPath string) bool {
|
||||
if strings.Contains(fullPath, "..") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
69
server/apis/download.go
Normal file
69
server/apis/download.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/httpworker"
|
||||
)
|
||||
|
||||
func (srv *SrvShare) DownloadHandler(res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
srv.Http.Fill(httputil.Err404, res)
|
||||
}
|
||||
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&httpworker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(srv.Download),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httputil.Err503, res)
|
||||
}
|
||||
|
||||
// using WriteTimeout instead of Timeout
|
||||
// After timeout, connection will be lost, and worker will fail to write and return
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.WriteTimeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httputil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) Download(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) ||
|
||||
!srv.Walls.PassOpLimit(shareId, srv.Conf.OpIdDownload) {
|
||||
return httputil.Err429
|
||||
}
|
||||
|
||||
return srv.download(shareId, res, req)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) download(shareId string, res http.ResponseWriter, req *http.Request) interface{} {
|
||||
if !srv.IsValidShareId(shareId) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
fileInfo, found := srv.Index.Get(shareId)
|
||||
switch {
|
||||
case !found || fileInfo.State != fileidx.StateDone:
|
||||
return httputil.Err404
|
||||
case fileInfo.DownLimit == 0:
|
||||
return httputil.Err412
|
||||
default:
|
||||
updated, _ := srv.Index.DecrDownLimit(shareId)
|
||||
if updated != 1 {
|
||||
return httputil.Err500
|
||||
}
|
||||
}
|
||||
|
||||
err := srv.Downloader.ServeFile(res, req, fileInfo)
|
||||
srv.Err.IsErr(err)
|
||||
return 0
|
||||
}
|
271
server/apis/download_test.go
Normal file
271
server/apis/download_test.go
Normal file
|
@ -0,0 +1,271 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/cfg"
|
||||
"quickshare/server/libs/errutil"
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/logutil"
|
||||
"quickshare/server/libs/qtube"
|
||||
)
|
||||
|
||||
func initServiceForDownloadTest(config *cfg.Config, indexMap map[string]*fileidx.FileInfo, content string) *SrvShare {
|
||||
setDownloader := func(srv *SrvShare) {
|
||||
srv.Downloader = stubDownloader{Content: content}
|
||||
}
|
||||
|
||||
setIndex := func(srv *SrvShare) {
|
||||
srv.Index = fileidx.NewMemFileIndexWithMap(len(indexMap), indexMap)
|
||||
}
|
||||
|
||||
setFs := func(srv *SrvShare) {
|
||||
srv.Fs = &stubFsUtil{
|
||||
MockFile: &qtube.StubFile{
|
||||
Content: content,
|
||||
Offset: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
logger := logutil.NewSlog(os.Stdout, config.AppName)
|
||||
setLog := func(srv *SrvShare) {
|
||||
srv.Log = logger
|
||||
}
|
||||
|
||||
setErr := func(srv *SrvShare) {
|
||||
srv.Err = errutil.NewErrChecker(!config.Production, logger)
|
||||
}
|
||||
|
||||
return InitSrvShare(config, setDownloader, setIndex, setFs, setLog, setErr)
|
||||
}
|
||||
|
||||
func TestDownload(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
Content string
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response interface{}
|
||||
Body string
|
||||
}
|
||||
type testCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
testCase{
|
||||
Desc: "empty file index",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "file info not found",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"1": &fileidx.FileInfo{},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"1": &fileidx.FileInfo{},
|
||||
},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "file not found because of state=uploading",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: 1,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateUploading,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: 1,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateUploading,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "download failed because download limit = 0",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: 0,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: 0,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
Response: httputil.Err412,
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "succeed to download",
|
||||
Init: Init{
|
||||
Content: "content",
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: 1,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: 0,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
Response: 0,
|
||||
Body: "content",
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "succeed to download DownLimit == -1",
|
||||
Init: Init{
|
||||
Content: "content",
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: -1,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: -1,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "path",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
Response: 0,
|
||||
Body: "content",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForDownloadTest(conf, testCase.Init.IndexMap, testCase.Content)
|
||||
writer := &stubWriter{Headers: map[string][]string{}}
|
||||
response := srv.download(
|
||||
testCase.ShareId,
|
||||
writer,
|
||||
&http.Request{},
|
||||
)
|
||||
|
||||
// verify downlimit
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
info, _ := srv.Index.Get(testCase.ShareId)
|
||||
t.Fatalf(
|
||||
"download: index incorrect got=%v want=%v",
|
||||
info,
|
||||
testCase.Output.IndexMap[testCase.ShareId],
|
||||
)
|
||||
}
|
||||
|
||||
// verify response
|
||||
if response != testCase.Output.Response {
|
||||
t.Fatalf(
|
||||
"download: response incorrect response=%v testCase=%v",
|
||||
response,
|
||||
testCase.Output.Response,
|
||||
)
|
||||
}
|
||||
|
||||
// verify writerContent
|
||||
if string(writer.Response) != testCase.Output.Body {
|
||||
t.Fatalf(
|
||||
"download: body incorrect got=%v want=%v",
|
||||
string(writer.Response),
|
||||
testCase.Output.Body,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
234
server/apis/file_info.go
Normal file
234
server/apis/file_info.go
Normal file
|
@ -0,0 +1,234 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/httpworker"
|
||||
)
|
||||
|
||||
func (srv *SrvShare) FileInfoHandler(res http.ResponseWriter, req *http.Request) {
|
||||
tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken)
|
||||
if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) ||
|
||||
!srv.Walls.PassLoginCheck(tokenStr, req) {
|
||||
srv.Http.Fill(httputil.Err429, res)
|
||||
return
|
||||
}
|
||||
|
||||
todo := func(res http.ResponseWriter, req *http.Request) interface{} { return httputil.Err404 }
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
todo = srv.List
|
||||
case http.MethodDelete:
|
||||
todo = srv.Del
|
||||
case http.MethodPatch:
|
||||
act := req.FormValue(srv.Conf.KeyAct)
|
||||
switch act {
|
||||
case srv.Conf.ActShadowId:
|
||||
todo = srv.ShadowId
|
||||
case srv.Conf.ActPublishId:
|
||||
todo = srv.PublishId
|
||||
case srv.Conf.ActSetDownLimit:
|
||||
todo = srv.SetDownLimit
|
||||
case srv.Conf.ActAddLocalFiles:
|
||||
todo = srv.AddLocalFiles
|
||||
default:
|
||||
srv.Http.Fill(httputil.Err404, res)
|
||||
return
|
||||
}
|
||||
default:
|
||||
srv.Http.Fill(httputil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&httpworker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(todo),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httputil.Err503, res)
|
||||
}
|
||||
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httputil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
type ResInfos struct {
|
||||
List []*fileidx.FileInfo
|
||||
}
|
||||
|
||||
func (srv *SrvShare) List(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdGetFInfo) {
|
||||
return httputil.Err429
|
||||
}
|
||||
|
||||
return srv.list()
|
||||
}
|
||||
|
||||
func (srv *SrvShare) list() interface{} {
|
||||
infos := make([]*fileidx.FileInfo, 0)
|
||||
for _, info := range srv.Index.List() {
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
return &ResInfos{List: infos}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) Del(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
if !srv.Walls.PassOpLimit(shareId, srv.Conf.OpIdDelFInfo) {
|
||||
return httputil.Err504
|
||||
}
|
||||
|
||||
return srv.del(shareId)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) del(shareId string) interface{} {
|
||||
if !srv.IsValidShareId(shareId) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
fileInfo, found := srv.Index.Get(shareId)
|
||||
if !found {
|
||||
return httputil.Err404
|
||||
}
|
||||
|
||||
srv.Index.Del(shareId)
|
||||
fullPath := filepath.Join(srv.Conf.PathLocal, fileInfo.PathLocal)
|
||||
if !srv.Fs.DelFile(fullPath) {
|
||||
// TODO: may log file name because file not exist or delete is not authenticated
|
||||
return httputil.Err500
|
||||
}
|
||||
|
||||
return httputil.Ok200
|
||||
}
|
||||
|
||||
func (srv *SrvShare) ShadowId(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdOpFInfo) {
|
||||
return httputil.Err429
|
||||
}
|
||||
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
return srv.shadowId(shareId)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) shadowId(shareId string) interface{} {
|
||||
if !srv.IsValidShareId(shareId) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
info, found := srv.Index.Get(shareId)
|
||||
if !found {
|
||||
return httputil.Err404
|
||||
}
|
||||
|
||||
secretId := srv.Encryptor.Encrypt(
|
||||
[]byte(fmt.Sprintf("%s%s", info.PathLocal, genPwd())),
|
||||
)
|
||||
if !srv.Index.SetId(info.Id, secretId) {
|
||||
return httputil.Err412
|
||||
}
|
||||
|
||||
return &ShareInfo{ShareId: secretId}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) PublishId(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdOpFInfo) {
|
||||
return httputil.Err429
|
||||
}
|
||||
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
return srv.publishId(shareId)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) publishId(shareId string) interface{} {
|
||||
if !srv.IsValidShareId(shareId) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
info, found := srv.Index.Get(shareId)
|
||||
if !found {
|
||||
return httputil.Err404
|
||||
}
|
||||
|
||||
publicId := srv.Encryptor.Encrypt([]byte(info.PathLocal))
|
||||
if !srv.Index.SetId(info.Id, publicId) {
|
||||
return httputil.Err412
|
||||
}
|
||||
|
||||
return &ShareInfo{ShareId: publicId}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) SetDownLimit(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdOpFInfo) {
|
||||
return httputil.Err429
|
||||
}
|
||||
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
downLimit64, downLimitParseErr := strconv.ParseInt(req.FormValue(srv.Conf.KeyDownLimit), 10, 32)
|
||||
downLimit := int(downLimit64)
|
||||
if srv.Err.IsErr(downLimitParseErr) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
return srv.setDownLimit(shareId, downLimit)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) setDownLimit(shareId string, downLimit int) interface{} {
|
||||
if !srv.IsValidShareId(shareId) || !srv.IsValidDownLimit(downLimit) {
|
||||
return httputil.Err400
|
||||
}
|
||||
|
||||
if !srv.Index.SetDownLimit(shareId, downLimit) {
|
||||
return httputil.Err404
|
||||
}
|
||||
return httputil.Ok200
|
||||
}
|
||||
|
||||
func (srv *SrvShare) AddLocalFiles(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
return srv.AddLocalFilesImp()
|
||||
}
|
||||
|
||||
func (srv *SrvShare) AddLocalFilesImp() interface{} {
|
||||
infos, err := srv.Fs.Readdir(srv.Conf.PathLocal, srv.Conf.LocalFileLimit)
|
||||
if srv.Err.IsErr(err) {
|
||||
panic(fmt.Sprintf("fail to readdir: %v", err))
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
info.DownLimit = srv.Conf.DownLimit
|
||||
info.State = fileidx.StateDone
|
||||
info.PathLocal = info.PathLocal
|
||||
info.Id = srv.Encryptor.Encrypt([]byte(info.PathLocal))
|
||||
|
||||
addRet := srv.Index.Add(info)
|
||||
switch {
|
||||
case addRet == 0 || addRet == -1:
|
||||
// TODO: return files not added
|
||||
continue
|
||||
case addRet == 1:
|
||||
break
|
||||
default:
|
||||
return httputil.Err500
|
||||
}
|
||||
}
|
||||
|
||||
return httputil.Ok200
|
||||
}
|
||||
|
||||
func genPwd() string {
|
||||
return fmt.Sprintf("%d%d%d%d", rand.Intn(10), rand.Intn(10), rand.Intn(10), rand.Intn(10))
|
||||
}
|
584
server/apis/file_info_test.go
Normal file
584
server/apis/file_info_test.go
Normal file
|
@ -0,0 +1,584 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/cfg"
|
||||
"quickshare/server/libs/errutil"
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/logutil"
|
||||
)
|
||||
|
||||
const mockShadowId = "shadowId"
|
||||
const mockPublicId = "publicId"
|
||||
|
||||
func initServiceForFileInfoTest(
|
||||
config *cfg.Config,
|
||||
indexMap map[string]*fileidx.FileInfo,
|
||||
useShadowEnc bool,
|
||||
localFileInfos []*fileidx.FileInfo,
|
||||
) *SrvShare {
|
||||
setIndex := func(srv *SrvShare) {
|
||||
srv.Index = fileidx.NewMemFileIndexWithMap(len(indexMap), indexMap)
|
||||
}
|
||||
|
||||
setFs := func(srv *SrvShare) {
|
||||
srv.Fs = &stubFsUtil{MockLocalFileInfos: localFileInfos}
|
||||
}
|
||||
|
||||
logger := logutil.NewSlog(os.Stdout, config.AppName)
|
||||
setLog := func(srv *SrvShare) {
|
||||
srv.Log = logger
|
||||
}
|
||||
|
||||
errChecker := errutil.NewErrChecker(!config.Production, logger)
|
||||
setErr := func(srv *SrvShare) {
|
||||
srv.Err = errChecker
|
||||
}
|
||||
|
||||
var setEncryptor AddDep
|
||||
if useShadowEnc {
|
||||
setEncryptor = func(srv *SrvShare) {
|
||||
srv.Encryptor = &stubEncryptor{MockResult: mockShadowId}
|
||||
}
|
||||
} else {
|
||||
setEncryptor = func(srv *SrvShare) {
|
||||
srv.Encryptor = &stubEncryptor{MockResult: mockPublicId}
|
||||
}
|
||||
}
|
||||
|
||||
return InitSrvShare(config, setIndex, setFs, setEncryptor, setLog, setErr)
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type TestCase struct {
|
||||
Desc string
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []TestCase{
|
||||
TestCase{
|
||||
Desc: "success",
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
},
|
||||
"1": &fileidx.FileInfo{
|
||||
Id: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForFileInfoTest(conf, testCase.Output.IndexMap, true, []*fileidx.FileInfo{})
|
||||
response := srv.list()
|
||||
resInfos := response.(*ResInfos)
|
||||
|
||||
for _, info := range resInfos.List {
|
||||
infoFromSrv, found := srv.Index.Get(info.Id)
|
||||
if !found || infoFromSrv.Id != info.Id {
|
||||
t.Fatalf("list: file infos are not identical")
|
||||
}
|
||||
}
|
||||
|
||||
if len(resInfos.List) != len(srv.Index.List()) {
|
||||
t.Fatalf("list: file infos are not identical")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDel(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response httputil.MsgRes
|
||||
}
|
||||
type TestCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []TestCase{
|
||||
TestCase{
|
||||
Desc: "success",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
},
|
||||
"1": &fileidx.FileInfo{
|
||||
Id: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"1": &fileidx.FileInfo{
|
||||
Id: "1",
|
||||
},
|
||||
},
|
||||
Response: httputil.Ok200,
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
Desc: "not found",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"1": &fileidx.FileInfo{
|
||||
Id: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"1": &fileidx.FileInfo{
|
||||
Id: "1",
|
||||
},
|
||||
},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, true, []*fileidx.FileInfo{})
|
||||
response := srv.del(testCase.ShareId)
|
||||
res := response.(httputil.MsgRes)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf("del: index incorrect")
|
||||
}
|
||||
|
||||
if res != testCase.Output.Response {
|
||||
t.Fatalf("del: response incorrect got: %v, want: %v", res, testCase.Output.Response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowId(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response interface{}
|
||||
}
|
||||
type TestCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []TestCase{
|
||||
TestCase{
|
||||
Desc: "success",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
mockShadowId: &fileidx.FileInfo{
|
||||
Id: mockShadowId,
|
||||
},
|
||||
},
|
||||
Response: &ShareInfo{
|
||||
ShareId: mockShadowId,
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
Desc: "original id not exists",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
Desc: "dest id exists",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
},
|
||||
mockShadowId: &fileidx.FileInfo{
|
||||
Id: mockShadowId,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
},
|
||||
mockShadowId: &fileidx.FileInfo{
|
||||
Id: mockShadowId,
|
||||
},
|
||||
},
|
||||
Response: httputil.Err412,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, true, []*fileidx.FileInfo{})
|
||||
response := srv.shadowId(testCase.ShareId)
|
||||
|
||||
switch response.(type) {
|
||||
case *ShareInfo:
|
||||
res := response.(*ShareInfo)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
info, found := srv.Index.Get(mockShadowId)
|
||||
t.Fatalf(
|
||||
"shadowId: index incorrect got %v found: %v want %v",
|
||||
info,
|
||||
found,
|
||||
testCase.Output.IndexMap[mockShadowId],
|
||||
)
|
||||
}
|
||||
|
||||
if res.ShareId != mockShadowId {
|
||||
t.Fatalf("shadowId: mockId incorrect")
|
||||
}
|
||||
|
||||
case httputil.MsgRes:
|
||||
res := response.(httputil.MsgRes)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf("shadowId: map not identical")
|
||||
}
|
||||
|
||||
if res != testCase.Output.Response {
|
||||
t.Fatalf("shadowId: response incorrect")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("shadowId: return type not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishId(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response interface{}
|
||||
}
|
||||
type TestCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []TestCase{
|
||||
TestCase{
|
||||
Desc: "success",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
mockShadowId: &fileidx.FileInfo{
|
||||
Id: mockShadowId,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: mockShadowId,
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
mockPublicId: &fileidx.FileInfo{
|
||||
Id: mockPublicId,
|
||||
},
|
||||
},
|
||||
Response: &ShareInfo{
|
||||
ShareId: mockPublicId,
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
Desc: "original id not exists",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
Desc: "dest id exists",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
mockShadowId: &fileidx.FileInfo{
|
||||
Id: mockShadowId,
|
||||
},
|
||||
mockPublicId: &fileidx.FileInfo{
|
||||
Id: mockPublicId,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: mockShadowId,
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
mockShadowId: &fileidx.FileInfo{
|
||||
Id: mockShadowId,
|
||||
},
|
||||
mockPublicId: &fileidx.FileInfo{
|
||||
Id: mockPublicId,
|
||||
},
|
||||
},
|
||||
Response: httputil.Err412,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, false, []*fileidx.FileInfo{})
|
||||
response := srv.publishId(testCase.ShareId)
|
||||
|
||||
switch response.(type) {
|
||||
case *ShareInfo:
|
||||
res := response.(*ShareInfo)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
info, found := srv.Index.Get(mockPublicId)
|
||||
t.Fatalf(
|
||||
"shadowId: index incorrect got %v found: %v want %v",
|
||||
info,
|
||||
found,
|
||||
testCase.Output.IndexMap[mockPublicId],
|
||||
)
|
||||
}
|
||||
|
||||
if res.ShareId != mockPublicId {
|
||||
t.Fatalf("shadowId: mockId incorrect", res.ShareId, mockPublicId)
|
||||
}
|
||||
|
||||
case httputil.MsgRes:
|
||||
res := response.(httputil.MsgRes)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf("shadowId: map not identical")
|
||||
}
|
||||
|
||||
if res != testCase.Output.Response {
|
||||
t.Fatalf("shadowId: response incorrect got: %v want: %v", res, testCase.Output.Response)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("shadowId: return type not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDownLimit(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
mockDownLimit := 100
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
DownLimit int
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response httputil.MsgRes
|
||||
}
|
||||
type TestCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []TestCase{
|
||||
TestCase{
|
||||
Desc: "success",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
DownLimit: mockDownLimit,
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
"0": &fileidx.FileInfo{
|
||||
Id: "0",
|
||||
DownLimit: mockDownLimit,
|
||||
},
|
||||
},
|
||||
Response: httputil.Ok200,
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
Desc: "not found",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: "0",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, true, []*fileidx.FileInfo{})
|
||||
response := srv.setDownLimit(testCase.ShareId, mockDownLimit)
|
||||
res := response.(httputil.MsgRes)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
info, _ := srv.Index.Get(testCase.ShareId)
|
||||
t.Fatalf(
|
||||
"setDownLimit: index incorrect got: %v want: %v",
|
||||
info,
|
||||
testCase.Output.IndexMap[testCase.ShareId],
|
||||
)
|
||||
}
|
||||
|
||||
if res != testCase.Output.Response {
|
||||
t.Fatalf("setDownLimit: response incorrect got: %v, want: %v", res, testCase.Output.Response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLocalFiles(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
Infos []*fileidx.FileInfo
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response httputil.MsgRes
|
||||
}
|
||||
type TestCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []TestCase{
|
||||
TestCase{
|
||||
Desc: "success",
|
||||
Init: Init{
|
||||
Infos: []*fileidx.FileInfo{
|
||||
&fileidx.FileInfo{
|
||||
Id: "",
|
||||
DownLimit: 0,
|
||||
ModTime: 13,
|
||||
PathLocal: "filename1",
|
||||
State: "",
|
||||
Uploaded: 13,
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
mockPublicId: &fileidx.FileInfo{
|
||||
Id: mockPublicId,
|
||||
DownLimit: conf.DownLimit,
|
||||
ModTime: 13,
|
||||
PathLocal: filepath.Join(conf.PathLocal, "filename1"),
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 13,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForFileInfoTest(conf, testCase.Output.IndexMap, false, testCase.Init.Infos)
|
||||
response := srv.AddLocalFilesImp()
|
||||
res := response.(httputil.MsgRes)
|
||||
|
||||
if res.Code != 200 {
|
||||
t.Fatalf("addLocalFiles: code not correct")
|
||||
}
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf(
|
||||
"addLocalFiles: indexes not identical got: %v want: %v",
|
||||
srv.Index.List(),
|
||||
testCase.Output.IndexMap,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
145
server/apis/service.go
Normal file
145
server/apis/service.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/cfg"
|
||||
"quickshare/server/libs/encrypt"
|
||||
"quickshare/server/libs/errutil"
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/fsutil"
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/httpworker"
|
||||
"quickshare/server/libs/limiter"
|
||||
"quickshare/server/libs/logutil"
|
||||
"quickshare/server/libs/qtube"
|
||||
"quickshare/server/libs/walls"
|
||||
)
|
||||
|
||||
type AddDep func(*SrvShare)
|
||||
|
||||
func NewSrvShare(config *cfg.Config) *SrvShare {
|
||||
logger := logutil.NewSlog(os.Stdout, config.AppName)
|
||||
setLog := func(srv *SrvShare) {
|
||||
srv.Log = logger
|
||||
}
|
||||
|
||||
errChecker := errutil.NewErrChecker(!config.Production, logger)
|
||||
setErr := func(srv *SrvShare) {
|
||||
srv.Err = errChecker
|
||||
}
|
||||
|
||||
setWorkerPool := func(srv *SrvShare) {
|
||||
workerPoolSize := config.WorkerPoolSize
|
||||
taskQueueSize := config.TaskQueueSize
|
||||
srv.WorkerPool = httpworker.NewWorkerPool(workerPoolSize, taskQueueSize, logger)
|
||||
}
|
||||
|
||||
setWalls := func(srv *SrvShare) {
|
||||
encrypterMaker := encrypt.JwtEncrypterMaker
|
||||
ipLimiter := limiter.NewRateLimiter(
|
||||
config.LimiterCap,
|
||||
config.LimiterTtl,
|
||||
config.LimiterCyc,
|
||||
config.BucketCap,
|
||||
config.SpecialCaps,
|
||||
)
|
||||
opLimiter := limiter.NewRateLimiter(
|
||||
config.LimiterCap,
|
||||
config.LimiterTtl,
|
||||
config.LimiterCyc,
|
||||
config.BucketCap,
|
||||
config.SpecialCaps,
|
||||
)
|
||||
srv.Walls = walls.NewAccessWalls(config, ipLimiter, opLimiter, encrypterMaker)
|
||||
}
|
||||
|
||||
setIndex := func(srv *SrvShare) {
|
||||
srv.Index = fileidx.NewMemFileIndex(config.MaxShares)
|
||||
}
|
||||
|
||||
fs := fsutil.NewSimpleFs(errChecker)
|
||||
setFs := func(srv *SrvShare) {
|
||||
srv.Fs = fs
|
||||
}
|
||||
|
||||
setDownloader := func(srv *SrvShare) {
|
||||
srv.Downloader = qtube.NewQTube(
|
||||
config.PathLocal,
|
||||
config.MaxDownBytesPerSec,
|
||||
config.MaxRangeLength,
|
||||
fs,
|
||||
)
|
||||
}
|
||||
|
||||
setEncryptor := func(srv *SrvShare) {
|
||||
srv.Encryptor = &encrypt.HmacEncryptor{Key: config.SecretKeyByte}
|
||||
}
|
||||
|
||||
setHttp := func(srv *SrvShare) {
|
||||
srv.Http = &httputil.QHttpUtil{
|
||||
CookieDomain: config.CookieDomain,
|
||||
CookieHttpOnly: config.CookieHttpOnly,
|
||||
CookieMaxAge: config.CookieMaxAge,
|
||||
CookiePath: config.CookiePath,
|
||||
CookieSecure: config.CookieSecure,
|
||||
Err: errChecker,
|
||||
}
|
||||
}
|
||||
|
||||
return InitSrvShare(config, setIndex, setWalls, setWorkerPool, setFs, setDownloader, setEncryptor, setLog, setErr, setHttp)
|
||||
}
|
||||
|
||||
func InitSrvShare(config *cfg.Config, addDeps ...AddDep) *SrvShare {
|
||||
srv := &SrvShare{}
|
||||
srv.Conf = config
|
||||
for _, addDep := range addDeps {
|
||||
addDep(srv)
|
||||
}
|
||||
|
||||
if !srv.Fs.MkdirAll(srv.Conf.PathLocal, os.FileMode(0775)) {
|
||||
panic("fail to make ./files/ folder")
|
||||
}
|
||||
|
||||
if res := srv.AddLocalFilesImp(); res != httputil.Ok200 {
|
||||
panic("fail to add local files")
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
type SrvShare struct {
|
||||
Conf *cfg.Config
|
||||
Encryptor encrypt.Encryptor
|
||||
Err errutil.ErrUtil
|
||||
Downloader qtube.Downloader
|
||||
Http httputil.HttpUtil
|
||||
Index fileidx.FileIndex
|
||||
Fs fsutil.FsUtil
|
||||
Log logutil.LogUtil
|
||||
Walls walls.Walls
|
||||
WorkerPool httpworker.Workers
|
||||
}
|
||||
|
||||
func (srv *SrvShare) Wrap(serviceFunc httpworker.ServiceFunc) httpworker.DoFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
body := serviceFunc(res, req)
|
||||
|
||||
if body != nil && body != 0 && srv.Http.Fill(body, res) <= 0 {
|
||||
log.Println("Wrap: fail to fill body", body, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetRemoteIp(addr string) string {
|
||||
addrParts := strings.Split(addr, ":")
|
||||
if len(addrParts) > 0 {
|
||||
return addrParts[0]
|
||||
}
|
||||
return "unknown ip"
|
||||
}
|
117
server/apis/test_helper.go
Normal file
117
server/apis/test_helper.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/qtube"
|
||||
)
|
||||
|
||||
type stubFsUtil struct {
|
||||
MockLocalFileInfos []*fileidx.FileInfo
|
||||
MockFile *qtube.StubFile
|
||||
}
|
||||
|
||||
var expectCreateFileName = ""
|
||||
|
||||
func (fs *stubFsUtil) CreateFile(fileName string) error {
|
||||
if fileName != expectCreateFileName {
|
||||
panic(
|
||||
fmt.Sprintf("CreateFile: got: %s expect: %s", fileName, expectCreateFileName),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *stubFsUtil) CopyChunkN(fullPath string, chunk io.Reader, start int64, len int64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (fs *stubFsUtil) ServeFile(res http.ResponseWriter, req *http.Request, fileName string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *stubFsUtil) DelFile(fullPath string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (fs *stubFsUtil) MkdirAll(path string, mode os.FileMode) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (fs *stubFsUtil) Readdir(dirname string, n int) ([]*fileidx.FileInfo, error) {
|
||||
return fs.MockLocalFileInfos, nil
|
||||
}
|
||||
|
||||
func (fs *stubFsUtil) Open(filePath string) (qtube.ReadSeekCloser, error) {
|
||||
return fs.MockFile, nil
|
||||
}
|
||||
|
||||
type stubWriter struct {
|
||||
Headers http.Header
|
||||
Response []byte
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (w *stubWriter) Header() http.Header {
|
||||
return w.Headers
|
||||
}
|
||||
|
||||
func (w *stubWriter) Write(body []byte) (int, error) {
|
||||
w.Response = append(w.Response, body...)
|
||||
return len(body), nil
|
||||
}
|
||||
|
||||
func (w *stubWriter) WriteHeader(statusCode int) {
|
||||
w.StatusCode = statusCode
|
||||
}
|
||||
|
||||
type stubDownloader struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
func (d stubDownloader) ServeFile(w http.ResponseWriter, r *http.Request, fileInfo *fileidx.FileInfo) error {
|
||||
_, err := w.Write([]byte(d.Content))
|
||||
return err
|
||||
}
|
||||
|
||||
func sameInfoWithoutTime(info1, info2 *fileidx.FileInfo) bool {
|
||||
return info1.Id == info2.Id &&
|
||||
info1.DownLimit == info2.DownLimit &&
|
||||
info1.PathLocal == info2.PathLocal &&
|
||||
info1.State == info2.State &&
|
||||
info1.Uploaded == info2.Uploaded
|
||||
}
|
||||
|
||||
func sameMap(map1, map2 map[string]*fileidx.FileInfo) bool {
|
||||
for key, info1 := range map1 {
|
||||
info2, found := map2[key]
|
||||
if !found || !sameInfoWithoutTime(info1, info2) {
|
||||
fmt.Printf("infos are not same: \n%v \n%v", info1, info2)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for key, info2 := range map2 {
|
||||
info1, found := map1[key]
|
||||
if !found || !sameInfoWithoutTime(info1, info2) {
|
||||
fmt.Printf("infos are not same: \n%v \n%v", info1, info2)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type stubEncryptor struct {
|
||||
MockResult string
|
||||
}
|
||||
|
||||
func (enc *stubEncryptor) Encrypt(content []byte) string {
|
||||
return enc.MockResult
|
||||
}
|
253
server/apis/upload.go
Normal file
253
server/apis/upload.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/encrypt"
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/fsutil"
|
||||
httpUtil "quickshare/server/libs/httputil"
|
||||
worker "quickshare/server/libs/httpworker"
|
||||
)
|
||||
|
||||
const DefaultId = "0"
|
||||
|
||||
type ByteRange struct {
|
||||
ShareId string
|
||||
Start int64
|
||||
Length int64
|
||||
}
|
||||
|
||||
type ShareInfo struct {
|
||||
ShareId string
|
||||
}
|
||||
|
||||
func (srv *SrvShare) StartUploadHandler(res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
srv.Http.Fill(httpUtil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken)
|
||||
ipPass := srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr))
|
||||
loginPass := srv.Walls.PassLoginCheck(tokenStr, req)
|
||||
opPass := srv.Walls.PassOpLimit(GetRemoteIp(req.RemoteAddr), srv.Conf.OpIdUpload)
|
||||
if !ipPass || !loginPass || !opPass {
|
||||
srv.Http.Fill(httpUtil.Err429, res)
|
||||
return
|
||||
}
|
||||
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&worker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(srv.StartUpload),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httpUtil.Err503, res)
|
||||
}
|
||||
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httpUtil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) UploadHandler(res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
srv.Http.Fill(httpUtil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken)
|
||||
ipPass := srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr))
|
||||
loginPass := srv.Walls.PassLoginCheck(tokenStr, req)
|
||||
opPass := srv.Walls.PassOpLimit(GetRemoteIp(req.RemoteAddr), srv.Conf.OpIdUpload)
|
||||
if !ipPass || !loginPass || !opPass {
|
||||
srv.Http.Fill(httpUtil.Err429, res)
|
||||
return
|
||||
}
|
||||
|
||||
multiFormErr := req.ParseMultipartForm(srv.Conf.ParseFormBufSize)
|
||||
if srv.Err.IsErr(multiFormErr) {
|
||||
srv.Http.Fill(httpUtil.Err400, res)
|
||||
return
|
||||
}
|
||||
|
||||
srv.Log.Println("form", req.Form)
|
||||
srv.Log.Println("pform", req.PostForm)
|
||||
srv.Log.Println("mform", req.MultipartForm)
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&worker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(srv.Upload),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httpUtil.Err503, res)
|
||||
}
|
||||
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httpUtil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) FinishUploadHandler(res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
srv.Http.Fill(httpUtil.Err404, res)
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken)
|
||||
ipPass := srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr))
|
||||
loginPass := srv.Walls.PassLoginCheck(tokenStr, req)
|
||||
opPass := srv.Walls.PassOpLimit(GetRemoteIp(req.RemoteAddr), srv.Conf.OpIdUpload)
|
||||
if !ipPass || !loginPass || !opPass {
|
||||
srv.Http.Fill(httpUtil.Err429, res)
|
||||
return
|
||||
}
|
||||
|
||||
ack := make(chan error, 1)
|
||||
ok := srv.WorkerPool.Put(&worker.Task{
|
||||
Ack: ack,
|
||||
Do: srv.Wrap(srv.FinishUpload),
|
||||
Res: res,
|
||||
Req: req,
|
||||
})
|
||||
if !ok {
|
||||
srv.Http.Fill(httpUtil.Err503, res)
|
||||
}
|
||||
|
||||
execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond)
|
||||
if srv.Err.IsErr(execErr) {
|
||||
srv.Http.Fill(httpUtil.Err500, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) StartUpload(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
return srv.startUpload(req.FormValue(srv.Conf.KeyFileName))
|
||||
}
|
||||
|
||||
func (srv *SrvShare) startUpload(fileName string) interface{} {
|
||||
if !IsValidFileName(fileName) {
|
||||
return httpUtil.Err400
|
||||
}
|
||||
|
||||
id := DefaultId
|
||||
if srv.Conf.Production {
|
||||
id = genInfoId(fileName, srv.Conf.SecretKeyByte)
|
||||
}
|
||||
|
||||
info := &fileidx.FileInfo{
|
||||
Id: id,
|
||||
DownLimit: srv.Conf.DownLimit,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: fileName,
|
||||
Uploaded: 0,
|
||||
State: fileidx.StateStarted,
|
||||
}
|
||||
|
||||
switch srv.Index.Add(info) {
|
||||
case 0:
|
||||
// go on
|
||||
case -1:
|
||||
return httpUtil.Err412
|
||||
case 1:
|
||||
return httpUtil.Err500 // TODO: use correct status code
|
||||
default:
|
||||
srv.Index.Del(id)
|
||||
return httpUtil.Err500
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(srv.Conf.PathLocal, info.PathLocal)
|
||||
createFileErr := srv.Fs.CreateFile(fullPath)
|
||||
switch {
|
||||
case createFileErr == fsutil.ErrExists:
|
||||
srv.Index.Del(id)
|
||||
return httpUtil.Err412
|
||||
case createFileErr == fsutil.ErrUnknown:
|
||||
srv.Index.Del(id)
|
||||
return httpUtil.Err500
|
||||
default:
|
||||
srv.Index.SetState(id, fileidx.StateUploading)
|
||||
return &ByteRange{
|
||||
ShareId: id,
|
||||
Start: 0,
|
||||
Length: srv.Conf.MaxUpBytesPerSec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) Upload(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
start, startErr := strconv.ParseInt(req.FormValue(srv.Conf.KeyStart), 10, 64)
|
||||
length, lengthErr := strconv.ParseInt(req.FormValue(srv.Conf.KeyLen), 10, 64)
|
||||
chunk, _, chunkErr := req.FormFile(srv.Conf.KeyChunk)
|
||||
|
||||
if srv.Err.IsErr(startErr) ||
|
||||
srv.Err.IsErr(lengthErr) ||
|
||||
srv.Err.IsErr(chunkErr) {
|
||||
return httpUtil.Err400
|
||||
}
|
||||
|
||||
return srv.upload(shareId, start, length, chunk)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) upload(shareId string, start int64, length int64, chunk io.Reader) interface{} {
|
||||
if !srv.IsValidShareId(shareId) {
|
||||
return httpUtil.Err400
|
||||
}
|
||||
|
||||
fileInfo, found := srv.Index.Get(shareId)
|
||||
if !found {
|
||||
return httpUtil.Err404
|
||||
}
|
||||
|
||||
if !srv.IsValidStart(start, fileInfo.Uploaded) || !srv.IsValidLength(length) {
|
||||
return httpUtil.Err400
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(srv.Conf.PathLocal, fileInfo.PathLocal)
|
||||
if !srv.Fs.CopyChunkN(fullPath, chunk, start, length) {
|
||||
return httpUtil.Err500
|
||||
}
|
||||
|
||||
if srv.Index.IncrUploaded(shareId, length) == 0 {
|
||||
return httpUtil.Err404
|
||||
}
|
||||
|
||||
return &ByteRange{
|
||||
ShareId: shareId,
|
||||
Start: start + length,
|
||||
Length: srv.Conf.MaxUpBytesPerSec,
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *SrvShare) FinishUpload(res http.ResponseWriter, req *http.Request) interface{} {
|
||||
shareId := req.FormValue(srv.Conf.KeyShareId)
|
||||
return srv.finishUpload(shareId)
|
||||
}
|
||||
|
||||
func (srv *SrvShare) finishUpload(shareId string) interface{} {
|
||||
if !srv.Index.SetState(shareId, fileidx.StateDone) {
|
||||
return httpUtil.Err404
|
||||
}
|
||||
|
||||
return &ShareInfo{
|
||||
ShareId: shareId,
|
||||
}
|
||||
}
|
||||
|
||||
func genInfoId(content string, key []byte) string {
|
||||
encrypter := encrypt.HmacEncryptor{Key: key}
|
||||
return encrypter.Encrypt([]byte(content))
|
||||
}
|
368
server/apis/upload_test.go
Normal file
368
server/apis/upload_test.go
Normal file
|
@ -0,0 +1,368 @@
|
|||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
import (
|
||||
"quickshare/server/libs/cfg"
|
||||
"quickshare/server/libs/encrypt"
|
||||
"quickshare/server/libs/errutil"
|
||||
"quickshare/server/libs/fileidx"
|
||||
"quickshare/server/libs/httputil"
|
||||
"quickshare/server/libs/httpworker"
|
||||
"quickshare/server/libs/limiter"
|
||||
"quickshare/server/libs/logutil"
|
||||
"quickshare/server/libs/walls"
|
||||
)
|
||||
|
||||
const testCap = 3
|
||||
|
||||
func initServiceForUploadTest(config *cfg.Config, indexMap map[string]*fileidx.FileInfo) *SrvShare {
|
||||
logger := logutil.NewSlog(os.Stdout, config.AppName)
|
||||
setLog := func(srv *SrvShare) {
|
||||
srv.Log = logger
|
||||
}
|
||||
|
||||
setWorkerPool := func(srv *SrvShare) {
|
||||
workerPoolSize := config.WorkerPoolSize
|
||||
taskQueueSize := config.TaskQueueSize
|
||||
srv.WorkerPool = httpworker.NewWorkerPool(workerPoolSize, taskQueueSize, logger)
|
||||
}
|
||||
|
||||
setWalls := func(srv *SrvShare) {
|
||||
encrypterMaker := encrypt.JwtEncrypterMaker
|
||||
ipLimiter := limiter.NewRateLimiter(config.LimiterCap, config.LimiterTtl, config.LimiterCyc, config.BucketCap, map[int16]int16{})
|
||||
opLimiter := limiter.NewRateLimiter(config.LimiterCap, config.LimiterTtl, config.LimiterCyc, config.BucketCap, map[int16]int16{})
|
||||
srv.Walls = walls.NewAccessWalls(config, ipLimiter, opLimiter, encrypterMaker)
|
||||
}
|
||||
|
||||
setIndex := func(srv *SrvShare) {
|
||||
srv.Index = fileidx.NewMemFileIndexWithMap(len(indexMap)+testCap, indexMap)
|
||||
}
|
||||
|
||||
setFs := func(srv *SrvShare) {
|
||||
srv.Fs = &stubFsUtil{}
|
||||
}
|
||||
|
||||
setEncryptor := func(srv *SrvShare) {
|
||||
srv.Encryptor = &encrypt.HmacEncryptor{Key: config.SecretKeyByte}
|
||||
}
|
||||
|
||||
errChecker := errutil.NewErrChecker(!config.Production, logger)
|
||||
setErr := func(srv *SrvShare) {
|
||||
srv.Err = errChecker
|
||||
}
|
||||
|
||||
return InitSrvShare(config, setIndex, setWalls, setWorkerPool, setFs, setEncryptor, setLog, setErr)
|
||||
}
|
||||
|
||||
func TestStartUpload(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
FileName string
|
||||
}
|
||||
type Output struct {
|
||||
Response interface{}
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type testCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
testCase{
|
||||
Desc: "invalid file name",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
FileName: "",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err400,
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "succeed to start uploading",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
FileName: "filename",
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
DefaultId: &fileidx.FileInfo{
|
||||
Id: DefaultId,
|
||||
DownLimit: conf.DownLimit,
|
||||
ModTime: time.Now().UnixNano(),
|
||||
PathLocal: "filename",
|
||||
Uploaded: 0,
|
||||
State: fileidx.StateUploading,
|
||||
},
|
||||
},
|
||||
Response: &ByteRange{
|
||||
ShareId: DefaultId,
|
||||
Start: 0,
|
||||
Length: conf.MaxUpBytesPerSec,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForUploadTest(conf, testCase.Init.IndexMap)
|
||||
|
||||
// verify CreateFile
|
||||
expectCreateFileName = filepath.Join(conf.PathLocal, testCase.FileName)
|
||||
|
||||
response := srv.startUpload(testCase.FileName)
|
||||
|
||||
// verify index
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf("startUpload: index not equal got: %v, %v, expect: %v", srv.Index.List(), response, testCase.Output.IndexMap)
|
||||
}
|
||||
|
||||
// verify response
|
||||
switch expectRes := testCase.Output.Response.(type) {
|
||||
case *ByteRange:
|
||||
res := response.(*ByteRange)
|
||||
if res.ShareId != expectRes.ShareId ||
|
||||
res.Start != expectRes.Start ||
|
||||
res.Length != expectRes.Length {
|
||||
t.Fatalf(fmt.Sprintf("startUpload: res=%v expect=%v", res, expectRes))
|
||||
}
|
||||
case httputil.MsgRes:
|
||||
if response != expectRes {
|
||||
t.Fatalf(fmt.Sprintf("startUpload: reponse=%v expectRes=%v", response, expectRes))
|
||||
}
|
||||
default:
|
||||
t.Fatalf(fmt.Sprintf("startUpload: type not found: %T %T", testCase.Output.Response, httputil.Err400))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpload(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
Start int64
|
||||
Len int64
|
||||
Chunk io.Reader
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response interface{}
|
||||
}
|
||||
type testCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
testCase{
|
||||
Desc: "shareid does not exist",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: DefaultId,
|
||||
Start: 0,
|
||||
Len: 1,
|
||||
Chunk: strings.NewReader(""),
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "succeed",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
DefaultId: &fileidx.FileInfo{
|
||||
Id: DefaultId,
|
||||
DownLimit: conf.MaxShares,
|
||||
PathLocal: "path/filename",
|
||||
State: fileidx.StateUploading,
|
||||
Uploaded: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: DefaultId,
|
||||
Start: 0,
|
||||
Len: 1,
|
||||
Chunk: strings.NewReader("a"),
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
DefaultId: &fileidx.FileInfo{
|
||||
Id: DefaultId,
|
||||
DownLimit: conf.MaxShares,
|
||||
PathLocal: "path/filename",
|
||||
State: fileidx.StateUploading,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
Response: &ByteRange{
|
||||
ShareId: DefaultId,
|
||||
Start: 1,
|
||||
Length: conf.MaxUpBytesPerSec,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForUploadTest(conf, testCase.Init.IndexMap)
|
||||
|
||||
response := srv.upload(
|
||||
testCase.Input.ShareId,
|
||||
testCase.Input.Start,
|
||||
testCase.Input.Len,
|
||||
testCase.Input.Chunk,
|
||||
)
|
||||
|
||||
// TODO: not verified copyChunk
|
||||
|
||||
// verify index
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf("upload: index not identical got: %v want: %v", srv.Index.List(), testCase.Output.IndexMap)
|
||||
}
|
||||
// verify response
|
||||
switch response.(type) {
|
||||
case *ByteRange:
|
||||
br := testCase.Output.Response.(*ByteRange)
|
||||
res := response.(*ByteRange)
|
||||
if res.ShareId != br.ShareId || res.Start != br.Start || res.Length != br.Length {
|
||||
t.Fatalf(fmt.Sprintf("upload: response=%v expectRes=%v", res, br))
|
||||
}
|
||||
default:
|
||||
if response != testCase.Output.Response {
|
||||
t.Fatalf(fmt.Sprintf("upload: response=%v expectRes=%v", response, testCase.Output.Response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishUpload(t *testing.T) {
|
||||
conf := cfg.NewConfig()
|
||||
conf.Production = false
|
||||
|
||||
type Init struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
}
|
||||
type Input struct {
|
||||
ShareId string
|
||||
Start int64
|
||||
Len int64
|
||||
Chunk io.Reader
|
||||
}
|
||||
type Output struct {
|
||||
IndexMap map[string]*fileidx.FileInfo
|
||||
Response interface{}
|
||||
}
|
||||
type testCase struct {
|
||||
Desc string
|
||||
Init
|
||||
Input
|
||||
Output
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
testCase{
|
||||
Desc: "success",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
DefaultId: &fileidx.FileInfo{
|
||||
Id: DefaultId,
|
||||
DownLimit: conf.MaxShares,
|
||||
PathLocal: "path/filename",
|
||||
State: fileidx.StateUploading,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: DefaultId,
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{
|
||||
DefaultId: &fileidx.FileInfo{
|
||||
Id: DefaultId,
|
||||
DownLimit: conf.MaxShares,
|
||||
PathLocal: "path/filename",
|
||||
State: fileidx.StateDone,
|
||||
Uploaded: 1,
|
||||
},
|
||||
},
|
||||
Response: &ShareInfo{
|
||||
ShareId: DefaultId,
|
||||
},
|
||||
},
|
||||
},
|
||||
testCase{
|
||||
Desc: "shareId exists",
|
||||
Init: Init{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
},
|
||||
Input: Input{
|
||||
ShareId: DefaultId,
|
||||
},
|
||||
Output: Output{
|
||||
IndexMap: map[string]*fileidx.FileInfo{},
|
||||
Response: httputil.Err404,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv := initServiceForUploadTest(conf, testCase.Init.IndexMap)
|
||||
|
||||
response := srv.finishUpload(testCase.ShareId)
|
||||
|
||||
if !sameMap(srv.Index.List(), testCase.Output.IndexMap) {
|
||||
t.Fatalf("finishUpload: index not identical got: %v, want: %v", srv.Index.List(), testCase.Output.IndexMap)
|
||||
}
|
||||
|
||||
switch res := response.(type) {
|
||||
case httputil.MsgRes:
|
||||
expectRes := testCase.Output.Response.(httputil.MsgRes)
|
||||
if res != expectRes {
|
||||
t.Fatalf(fmt.Sprintf("finishUpload: reponse=%v expectRes=%v", res, expectRes))
|
||||
}
|
||||
case *ShareInfo:
|
||||
info, found := testCase.Output.IndexMap[res.ShareId]
|
||||
if !found || info.State != fileidx.StateDone {
|
||||
// TODO: should use isValidUrl or better to verify result
|
||||
t.Fatalf(fmt.Sprintf("finishUpload: share info is not correct: received: %v expect: %v", res.ShareId, testCase.ShareId))
|
||||
}
|
||||
default:
|
||||
t.Fatalf(fmt.Sprintf("finishUpload: type not found: %T %T", response, testCase.Output.Response))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue