• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gameap / gameap / 25960901307

16 May 2026 11:29AM UTC coverage: 76.887% (+0.2%) from 76.64%
25960901307

push

github

et-nik
audit logs tests

45395 of 59041 relevant lines covered (76.89%)

33895.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.61
/internal/api/filemanager/createfile/handler.go
1
package createfile
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "net/http"
7
        "os"
8
        "path/filepath"
9
        "strings"
10

11
        "github.com/gameap/gameap/internal/api/base"
12
        serversbase "github.com/gameap/gameap/internal/api/servers/base"
13
        "github.com/gameap/gameap/internal/daemon"
14
        "github.com/gameap/gameap/internal/domain"
15
        "github.com/gameap/gameap/internal/filters"
16
        "github.com/gameap/gameap/internal/repositories"
17
        "github.com/gameap/gameap/pkg/api"
18
        "github.com/gameap/gameap/pkg/auth"
19
        "github.com/pkg/errors"
20
)
21

22
type fileService interface {
23
        Upload(
24
                ctx context.Context, node *domain.Node, filePath string,
25
                content []byte, perms os.FileMode, owner daemon.OwnerOptions,
26
        ) error
27
        GetFileInfo(ctx context.Context, node *domain.Node, path string) (*daemon.FileDetails, error)
28
}
29

30
type Handler struct {
31
        serverFinder   *serversbase.ServerFinder
32
        abilityChecker *serversbase.AbilityChecker
33
        nodeRepo       repositories.NodeRepository
34
        daemonFiles    fileService
35
        responder      base.Responder
36
}
37

38
func NewHandler(
39
        serverRepo repositories.ServerRepository,
40
        nodeRepo repositories.NodeRepository,
41
        rbac base.RBAC,
42
        daemonFiles fileService,
43
        responder base.Responder,
44
) *Handler {
19✔
45
        return &Handler{
19✔
46
                serverFinder:   serversbase.NewServerFinder(serverRepo, rbac),
19✔
47
                abilityChecker: serversbase.NewAbilityChecker(rbac),
19✔
48
                nodeRepo:       nodeRepo,
19✔
49
                daemonFiles:    daemonFiles,
19✔
50
                responder:      responder,
19✔
51
        }
19✔
52
}
19✔
53

54
func (h *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
19✔
55
        ctx := r.Context()
19✔
56

19✔
57
        session := auth.SessionFromContext(ctx)
19✔
58
        if !session.IsAuthenticated() {
20✔
59
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
1✔
60
                        errors.New("user not authenticated"),
1✔
61
                        http.StatusUnauthorized,
1✔
62
                ))
1✔
63

1✔
64
                return
1✔
65
        }
1✔
66

67
        input := api.NewInputReader(r)
18✔
68

18✔
69
        serverID, err := input.ReadUint("server")
18✔
70
        if err != nil {
19✔
71
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
1✔
72
                        errors.WithMessage(err, "invalid server id"),
1✔
73
                        http.StatusBadRequest,
1✔
74
                ))
1✔
75

1✔
76
                return
1✔
77
        }
1✔
78

79
        server, err := h.serverFinder.FindUserServer(ctx, session.User, serverID)
17✔
80
        if err != nil {
19✔
81
                h.responder.WriteError(ctx, rw, err)
2✔
82

2✔
83
                return
2✔
84
        }
2✔
85

86
        err = h.abilityChecker.CheckOrError(
15✔
87
                ctx,
15✔
88
                session.User.ID,
15✔
89
                server.ID,
15✔
90
                []domain.AbilityName{domain.AbilityNameGameServerFiles},
15✔
91
        )
15✔
92
        if err != nil {
16✔
93
                h.responder.WriteError(ctx, rw, err)
1✔
94

1✔
95
                return
1✔
96
        }
1✔
97

98
        var req createFileRequest
14✔
99
        err = json.NewDecoder(r.Body).Decode(&req)
14✔
100
        if err != nil {
15✔
101
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
1✔
102
                        errors.WithMessage(err, "invalid request body"),
1✔
103
                        http.StatusBadRequest,
1✔
104
                ))
1✔
105

1✔
106
                return
1✔
107
        }
1✔
108

109
        if err = req.Validate(); err != nil {
15✔
110
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(err, http.StatusBadRequest))
2✔
111

2✔
112
                return
2✔
113
        }
2✔
114

115
        node, err := h.getNode(ctx, server.DSID)
11✔
116
        if err != nil {
12✔
117
                h.responder.WriteError(ctx, rw, err)
1✔
118

1✔
119
                return
1✔
120
        }
1✔
121

122
        response, err := h.createFile(ctx, node, server, &req)
10✔
123
        if err != nil {
14✔
124
                h.responder.WriteError(ctx, rw, err)
4✔
125

4✔
126
                return
4✔
127
        }
4✔
128

129
        h.responder.Write(ctx, rw, response)
6✔
130
}
131

132
func (h *Handler) getNode(ctx context.Context, nodeID uint) (*domain.Node, error) {
11✔
133
        nodes, err := h.nodeRepo.Find(ctx, &filters.FindNode{
11✔
134
                IDs: []uint{nodeID},
11✔
135
        }, nil, &filters.Pagination{
11✔
136
                Limit: 1,
11✔
137
        })
11✔
138
        if err != nil {
11✔
139
                return nil, errors.WithMessage(err, "failed to find node")
×
140
        }
×
141

142
        if len(nodes) == 0 {
12✔
143
                return nil, api.NewNotFoundError("node not found")
1✔
144
        }
1✔
145

146
        return &nodes[0], nil
10✔
147
}
148

149
func (h *Handler) createFile(
150
        ctx context.Context,
151
        node *domain.Node,
152
        server *domain.Server,
153
        req *createFileRequest,
154
) (createFileResponse, error) {
10✔
155
        if err := validatePath(req.Path); err != nil {
11✔
156
                return createFileResponse{}, api.WrapHTTPError(err, http.StatusBadRequest)
1✔
157
        }
1✔
158

159
        if err := validatePath(req.Name); err != nil {
10✔
160
                return createFileResponse{}, api.WrapHTTPError(err, http.StatusBadRequest)
1✔
161
        }
1✔
162

163
        relativePath := filepath.Join(req.Path, req.Name)
8✔
164
        fullPath := filepath.Join(node.WorkPath, server.Dir, relativePath)
8✔
165

8✔
166
        err := h.daemonFiles.Upload(ctx, node, fullPath, []byte{}, 0o644, daemon.OwnerFromServer(server))
8✔
167
        if err != nil {
9✔
168
                return createFileResponse{}, errors.WithMessage(err, "failed to create file")
1✔
169
        }
1✔
170

171
        fileInfo, err := h.daemonFiles.GetFileInfo(ctx, node, fullPath)
7✔
172
        if err != nil {
8✔
173
                return createFileResponse{}, errors.WithMessage(err, "failed to get file info")
1✔
174
        }
1✔
175

176
        return newCreateFileResponse(fileInfo, relativePath), nil
6✔
177
}
178

179
func validatePath(path string) error {
31✔
180
        if strings.Contains(path, "..") {
39✔
181
                return errors.New("path contains invalid directory traversal")
8✔
182
        }
8✔
183

184
        cleanPath := filepath.Clean(path)
23✔
185
        if strings.HasPrefix(cleanPath, "..") {
23✔
186
                return errors.New("path attempts to escape base directory")
×
187
        }
×
188

189
        return nil
23✔
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc