package route import ( "crypto/ecdsa" "log" "net/http" "net/url" "path" "path/filepath" "strconv" "strings" "github.com/IceWhaleTech/CasaOS/codegen" "github.com/IceWhaleTech/CasaOS/pkg/config" "github.com/IceWhaleTech/CasaOS/pkg/utils/file" "github.com/IceWhaleTech/CasaOS-Common/external" "github.com/IceWhaleTech/CasaOS-Common/utils/jwt" v2Route "github.com/IceWhaleTech/CasaOS/route/v2" "github.com/deepmap/oapi-codegen/pkg/middleware" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/labstack/echo/v4" echo_middleware "github.com/labstack/echo/v4/middleware" ) var ( _swagger *openapi3.T V2APIPath string V2DocPath string V3FilePath string ) func init() { swagger, err := codegen.GetSwagger() if err != nil { panic(err) } _swagger = swagger u, err := url.Parse(_swagger.Servers[0].URL) if err != nil { panic(err) } V2APIPath = strings.TrimRight(u.Path, "/") V2DocPath = "/doc" + V2APIPath V3FilePath = "/v3/file" } func InitV2Router() http.Handler { appManagement := v2Route.NewCasaOS() e := echo.New() e.Use((echo_middleware.CORSWithConfig(echo_middleware.CORSConfig{ AllowOrigins: []string{"*"}, AllowMethods: []string{echo.POST, echo.GET, echo.OPTIONS, echo.PUT, echo.DELETE}, AllowHeaders: []string{echo.HeaderAuthorization, echo.HeaderContentLength, echo.HeaderXCSRFToken, echo.HeaderContentType, echo.HeaderAccessControlAllowOrigin, echo.HeaderAccessControlAllowHeaders, echo.HeaderAccessControlAllowMethods, echo.HeaderConnection, echo.HeaderOrigin, echo.HeaderXRequestedWith}, ExposeHeaders: []string{echo.HeaderContentLength, echo.HeaderAccessControlAllowOrigin, echo.HeaderAccessControlAllowHeaders}, MaxAge: 172800, AllowCredentials: true, }))) e.Use(echo_middleware.Gzip()) e.Use(echo_middleware.Logger()) e.Use(echo_middleware.JWTWithConfig(echo_middleware.JWTConfig{ Skipper: func(c echo.Context) bool { return c.RealIP() == "::1" || c.RealIP() == "127.0.0.1" //return true }, ParseTokenFunc: func(token string, c echo.Context) (interface{}, error) { valid, claims, err := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) }) if err != nil || !valid { return nil, echo.ErrUnauthorized } c.Request().Header.Set("user_id", strconv.Itoa(claims.ID)) return claims, nil }, TokenLookupFuncs: []echo_middleware.ValuesExtractor{ func(c echo.Context) ([]string, error) { if len(c.Request().Header.Get(echo.HeaderAuthorization)) > 0 { return []string{c.Request().Header.Get(echo.HeaderAuthorization)}, nil } return []string{c.QueryParam("token")}, nil }, }, })) // e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { // return func(c echo.Context) error { // switch c.Request().Header.Get(echo.HeaderContentType) { // case common.MIMEApplicationYAML: // in case request contains a compose content in YAML // return middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{ // Options: openapi3filter.Options{ // AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, // // ExcludeRequestBody: true, // // ExcludeResponseBody: true, // }, // })(next)(c) // default: // return middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{ // Options: openapi3filter.Options{ // AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, // }, // })(next)(c) // } // } // }) e.Use(middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{ Options: openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc}, })) codegen.RegisterHandlersWithBaseURL(e, appManagement, V2APIPath) return e } func InitV2DocRouter(docHTML string, docYAML string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == V2DocPath { if _, err := w.Write([]byte(docHTML)); err != nil { w.WriteHeader(http.StatusInternalServerError) } return } if r.URL.Path == V2DocPath+"/openapi.yaml" { if _, err := w.Write([]byte(docYAML)); err != nil { w.WriteHeader(http.StatusInternalServerError) } } }) } func InitFile() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.URL.Query().Get("token") if len(token) == 0 { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message": "token not found"}`)) return } valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) }) if errs != nil || !valid { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message": "validation failure"}`)) return } filePath := r.URL.Query().Get("path") fileName := path.Base(filePath) w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName)) http.ServeFile(w, r, filePath) //http.ServeFile(w, r, filePath) }) } func InitDir() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.URL.Query().Get("token") if len(token) == 0 { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message": "token not found"}`)) return } valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) }) if errs != nil || !valid { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message": "validation failure"}`)) return } t := r.URL.Query().Get("format") files := r.URL.Query().Get("files") if len(files) == 0 { // w.JSON(common_err.CLIENT_ERROR, model.Result{ // Success: common_err.INVALID_PARAMS, // Message: common_err.GetMsg(common_err.INVALID_PARAMS), // }) return } list := strings.Split(files, ",") for _, v := range list { if !file.Exists(v) { // c.JSON(common_err.SERVICE_ERROR, model.Result{ // Success: common_err.FILE_DOES_NOT_EXIST, // Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST), // }) return } } w.Header().Add("Content-Type", "application/octet-stream") w.Header().Add("Content-Transfer-Encoding", "binary") w.Header().Add("Cache-Control", "no-cache") // handles only single files not folders and multiple files // if len(list) == 1 { // filePath := list[0] // info, err := os.Stat(filePath) // if err != nil { // w.JSON(http.StatusOK, model.Result{ // Success: common_err.FILE_DOES_NOT_EXIST, // Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST), // }) //return // } //} extension, ar, err := file.GetCompressionAlgorithm(t) if err != nil { // w.JSON(common_err.CLIENT_ERROR, model.Result{ // Success: common_err.INVALID_PARAMS, // Message: common_err.GetMsg(common_err.INVALID_PARAMS), // }) return } err = ar.Create(w) if err != nil { // c.JSON(common_err.SERVICE_ERROR, model.Result{ // Success: common_err.SERVICE_ERROR, // Message: common_err.GetMsg(common_err.SERVICE_ERROR), // Data: err.Error(), // }) return } defer ar.Close() commonDir := file.CommonPrefix(filepath.Separator, list...) currentPath := filepath.Base(commonDir) name := "_" + currentPath name += extension w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name)) for _, fname := range list { err = file.AddFile(ar, fname, commonDir) if err != nil { log.Printf("Failed to archive %s: %v", fname, err) } } }) }