diff --git a/build.sh b/build.sh index 844bda2..26a711b 100644 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -platforms=("windows/amd64" "windows/arm64" "darwin/amd64" "darwin/arm64" "linux/amd64" "linux/arm64") +platforms=("windows/amd64" "linux/amd64" "linux/arm64" "linux/arm") if [[ -d "./build" ]] then @@ -8,7 +8,6 @@ then fi mkdir build -cd build for platform in "${platforms[@]}" do @@ -16,9 +15,21 @@ do platform_split=(${platform//\// }) GOOS=${platform_split[0]} GOARCH=${platform_split[1]} - output_name='osc-'$GOOS'-'$GOARCH + output_name='./build/osc-'$GOOS'-'$GOARCH if [ $GOOS = "windows" ]; then output_name+='.exe' + go generate + env GOAMD64=v3 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=1 go build -o $output_name -a + else + if [ $GOARCH = "arm" ]; then + env GOARM=7 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a + else + if [ $GOARCH = "amd64" ]; then + env GOAMD64=v3 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a + else + env GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a + fi + fi fi - env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name -a ../main.go + done \ No newline at end of file diff --git a/common.go b/common.go new file mode 100644 index 0000000..d460eb9 --- /dev/null +++ b/common.go @@ -0,0 +1,26 @@ +package main + +import ( + "io" + "log" + "opensavecloudserver/config" + "opensavecloudserver/database" + "os" +) + +func InitCommon() { + f, err := os.OpenFile("server.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("error opening file: %v", err) + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Println(err) + } + }(f) + log.SetOutput(io.MultiWriter(os.Stdout, f)) + + config.Init() + database.Init() +} diff --git a/config/config.go b/config/config.go index d4e817f..7105bb2 100644 --- a/config/config.go +++ b/config/config.go @@ -38,7 +38,7 @@ type FeaturesConfiguration struct { var currentConfig *Configuration -func init() { +func Init() { path := flag.String("config", "./config.yml", "Set the configuration file path") flag.Parse() configYamlContent, err := os.ReadFile(*path) diff --git a/database/database.go b/database/database.go index 0abf8a7..cc24f4e 100644 --- a/database/database.go +++ b/database/database.go @@ -18,7 +18,7 @@ var db *gorm.DB const AdminRole string = "admin" const UserRole string = "user" -func init() { +func Init() { dbConfig := config.Database() var err error connectionString := "" diff --git a/go.mod b/go.mod index b4669e4..3f302f1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module opensavecloudserver go 1.18 require ( + github.com/getlantern/systray v1.2.1 github.com/go-chi/chi/v5 v5.0.7 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.3.0 @@ -13,7 +14,16 @@ require ( ) require ( + github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect + github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect + github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect + github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect + github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect + github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.4 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect ) diff --git a/go.sum b/go.sum index ad26ba0..62d4f9c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,25 @@ +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/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= +github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= +github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So= +github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= +github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk= +github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc= +github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0= +github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o= +github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc= +github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA= +github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA= +github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= +github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI= +github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -10,8 +28,18 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +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/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= golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8= golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= diff --git a/main.go b/main.go index dd8b120..616aa49 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,6 @@ +//go:build !windows +// +build !windows + package main import ( @@ -9,5 +12,6 @@ import ( func main() { fmt.Printf("Open Save Cloud (Server) %s (%s %s)\n", constant.Version, runtime.GOOS, runtime.GOARCH) + InitCommon() server.Serve() } diff --git a/main_windows.go b/main_windows.go new file mode 100644 index 0000000..afeb567 --- /dev/null +++ b/main_windows.go @@ -0,0 +1,47 @@ +//go:build windows + +package main + +import ( + _ "embed" + "github.com/getlantern/systray" + "opensavecloudserver/constant" + "opensavecloudserver/server" + "os" +) + +//go:generate go-winres make + +//go:embed tray.ico +var icon []byte + +func main() { + go func() { + InitCommon() + server.Serve() + }() + systray.Run(onReady, onExit) +} + +func onReady() { + systray.SetIcon(icon) + systray.SetTitle("Open Save Cloud Server") + systray.SetTooltip("Open Save Cloud Server") + systray.AddMenuItem("Open Save Cloud", "").Disable() + systray.AddMenuItem(constant.Version, "").Disable() + systray.AddSeparator() + mQuit := systray.AddMenuItem("Quit", "Quit the server") + select { + case <-mQuit.ClickedCh: + quit() + } +} + +func quit() { + systray.Quit() + os.Exit(0) +} + +func onExit() { + systray.Quit() +} diff --git a/server/data.go b/server/data.go index 11b576b..8018bc1 100644 --- a/server/data.go +++ b/server/data.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "time" "unicode/utf8" ) @@ -154,6 +155,11 @@ func UploadSave(w http.ResponseWriter, r *http.Request) { badRequest("The header X-Game-Save-Hash is missing", w, r) return } + archiveHash := strings.ToLower(r.Header.Get("X-Hash")) + if utf8.RuneCountInString(hash) == 0 { + badRequest("The header X-Hash is missing", w, r) + return + } game, err := database.GameInfoById(userId, gameId) if err != nil { internalServerError(w, r) @@ -172,7 +178,13 @@ func UploadSave(w http.ResponseWriter, r *http.Request) { log.Println(err) } }(file) - err = upload.ProcessFile(file, game) + err = upload.UploadToCache(file, game) + if err != nil { + internalServerError(w, r) + log.Println(err) + return + } + err = upload.ValidateAndMove(game, archiveHash) if err != nil { internalServerError(w, r) log.Println(err) @@ -216,6 +228,12 @@ func Download(w http.ResponseWriter, r *http.Request) { savePath := filepath.Join(config.Path().Storage, strconv.Itoa(userId), game.PathStorage) if _, err := os.Stat(savePath); err == nil { + hash, err := upload.FileHash(savePath) + if err != nil { + internalServerError(w, r) + log.Println(err) + return + } file, err := os.Open(savePath) if err != nil { internalServerError(w, r) @@ -228,6 +246,7 @@ func Download(w http.ResponseWriter, r *http.Request) { log.Println(err) } }(file) + w.Header().Add("X-Hash", strings.ToUpper(hash)) _, err = io.Copy(w, file) if err != nil { internalServerError(w, r) diff --git a/server/server.go b/server/server.go index 82032f8..008df41 100644 --- a/server/server.go +++ b/server/server.go @@ -36,35 +36,35 @@ func Serve() { r.Route("/system", func(systemRouter chi.Router) { systemRouter.Get("/information", Information) }) - r.Route("/user", func(secureRouter chi.Router) { - secureRouter.Use(authMiddleware) - secureRouter.Get("/information", UserInformation) - secureRouter.Post("/passwd", ChangePassword) + r.Route("/admin", func(adminRouter chi.Router) { + adminRouter.Use(adminMiddleware) + adminRouter.Post("/user", AddUser) + adminRouter.Post("/user/passwd/{id}", ChangeUserPassword) + adminRouter.Delete("/user/{id}", RemoveUser) + adminRouter.Get("/user/{id}", User) + adminRouter.Get("/users", AllUsers) + adminRouter.Get("/user/role/admin/{id}", SetAdmin) + adminRouter.Get("/user/role/user/{id}", SetNotAdmin) }) - r.Route("/admin", func(secureRouter chi.Router) { - secureRouter.Use(adminMiddleware) - secureRouter.Post("/user", AddUser) - secureRouter.Post("/user/passwd/{id}", ChangeUserPassword) - secureRouter.Delete("/user/{id}", RemoveUser) - secureRouter.Get("/user/{id}", User) - secureRouter.Get("/users", AllUsers) - secureRouter.Get("/user/role/admin/{id}", SetAdmin) - secureRouter.Get("/user/role/user/{id}", SetNotAdmin) - }) - r.Route("/game", func(secureRouter chi.Router) { + r.Group(func(secureRouter chi.Router) { secureRouter.Use(authMiddleware) - secureRouter.Post("/create", CreateGame) - secureRouter.Get("/all", AllGamesInformation) - secureRouter.Delete("/remove/{id}", RemoveGame) - secureRouter.Get("/info/{id}", GameInfoByID) - secureRouter.Post("/upload/init", AskForUpload) - secureRouter.Group(func(uploadRouter chi.Router) { - uploadRouter.Use(uploadMiddleware) - uploadRouter.Post("/upload", UploadSave) - uploadRouter.Get("/download", Download) + secureRouter.Route("/user", func(userRouter chi.Router) { + userRouter.Get("/information", UserInformation) + userRouter.Post("/passwd", ChangePassword) + }) + secureRouter.Route("/game", func(gameRouter chi.Router) { + gameRouter.Post("/create", CreateGame) + gameRouter.Get("/all", AllGamesInformation) + gameRouter.Delete("/remove/{id}", RemoveGame) + gameRouter.Get("/info/{id}", GameInfoByID) + gameRouter.Post("/upload/init", AskForUpload) + gameRouter.Group(func(uploadRouter chi.Router) { + uploadRouter.Use(uploadMiddleware) + uploadRouter.Post("/upload", UploadSave) + uploadRouter.Get("/download", Download) + }) }) }) - }) }) log.Println("Server is listening...") diff --git a/upload/upload.go b/upload/upload.go index 08225e8..6391882 100644 --- a/upload/upload.go +++ b/upload/upload.go @@ -1,6 +1,7 @@ package upload import ( + "crypto/sha512" "errors" "fmt" "github.com/google/uuid" @@ -107,7 +108,7 @@ func CheckUploadToken(uploadToken string) (int, bool) { return -1, false } -func ProcessFile(file multipart.File, game *database.Game) error { +func UploadToCache(file multipart.File, game *database.Game) error { filePath := path.Join(config.Path().Cache, strconv.Itoa(game.UserId)) if _, err := os.Stat(filePath); err != nil { err = os.Mkdir(filePath, 0766) @@ -126,13 +127,30 @@ func ProcessFile(file multipart.File, game *database.Game) error { log.Println(err) } }(f) - _, err = io.Copy(f, file) + if _, err := io.Copy(f, file); err != nil { + return err + } + return nil +} + +func ValidateAndMove(game *database.Game, hash string) error { + filePath := path.Join(config.Path().Cache, strconv.Itoa(game.UserId), game.PathStorage) + if err := checkHash(filePath, hash); err != nil { + return err + } + if err := moveToStorage(filePath, game); err != nil { + return err + } + return nil +} + +func checkHash(path, hash string) error { + h, err := FileHash(path) if err != nil { return err } - err = moveToStorage(filePath, game) - if err != nil { - return err + if h != hash { + return errors.New("hash is different") } return nil } @@ -187,6 +205,24 @@ func RemoveGame(userId int, game *database.Game) error { return os.Remove(filePath) } +func FileHash(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Println(err) + } + }(f) + h := sha512.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + // clearLocks clear lock of zombi upload func clearLocks() { mu.Lock() diff --git a/winres/icon.png b/winres/icon.png new file mode 100644 index 0000000..95a0376 Binary files /dev/null and b/winres/icon.png differ diff --git a/winres/icon_16.png b/winres/icon_16.png new file mode 100644 index 0000000..70ce390 Binary files /dev/null and b/winres/icon_16.png differ diff --git a/winres/icon_32.png b/winres/icon_32.png new file mode 100644 index 0000000..de319a1 Binary files /dev/null and b/winres/icon_32.png differ diff --git a/winres/icon_48.png b/winres/icon_48.png new file mode 100644 index 0000000..7b2e2e3 Binary files /dev/null and b/winres/icon_48.png differ diff --git a/winres/icon_64.png b/winres/icon_64.png new file mode 100644 index 0000000..6ebc87e Binary files /dev/null and b/winres/icon_64.png differ diff --git a/winres/winres.json b/winres/winres.json new file mode 100644 index 0000000..1466e05 --- /dev/null +++ b/winres/winres.json @@ -0,0 +1,59 @@ +{ + "RT_GROUP_ICON": { + "APP": { + "0000": [ + "icon_64.png", + "icon_48.png", + "icon_32.png", + "icon_16.png" + ] + }, + "OTHER": { + "0000": "icon.png" + } + }, + "RT_MANIFEST": { + "#1": { + "0409": { + "identity": { + "name": "Open Save Cloud Server", + "version": "1.0.0.0" + }, + "description": "", + "minimum-os": "win7", + "execution-level": "as invoker", + "ui-access": false, + "auto-elevate": false, + "dpi-awareness": "per monitor v2", + "disable-theming": false, + "disable-window-filtering": false, + "high-resolution-scrolling-aware": false, + "ultra-high-resolution-scrolling-aware": false, + "long-path-aware": false, + "printer-driver-isolation": false, + "gdi-scaling": false, + "segment-heap": false, + "use-common-controls-v6": false + } + } + }, + "RT_VERSION": { + "#1": { + "0000": { + "fixed": { + "file_version": "1.0.0.0", + "product_version": "1.0.0.0" + }, + "info": { + "0409": { + "CompanyName": "Aurelie Delhaie", + "FileDescription": "The server of Open Save Cloud", + "FileVersion": "1.0.0.0", + "ProductName": "Open Save Cloud Server", + "ProductVersion": "1.0.0.0" + } + } + } + } + } +} \ No newline at end of file