9
9
"io/fs"
10
10
"net/http"
11
11
"os"
12
- "path"
13
12
"path/filepath"
14
13
"strings"
15
14
"time"
@@ -46,28 +45,34 @@ type ResponseMessage struct {
46
45
Message string `json:"message"`
47
46
}
48
47
49
- type MkdirFS interface {
50
- fs.FS
51
- MkdirAll (path string , perm fs.FileMode ) error
52
- Open (name string ) (fs.File , error )
53
- OpenAtEnd (name string ) (fs.File , error )
48
+ type WritableFile interface {
49
+ io.WriteCloser
54
50
}
55
51
56
- type MkdirFsImpl struct {
57
- dir string
58
- fs. FS
52
+ type WriteFS interface {
53
+ OpenWritable ( name string ) ( WritableFile , error )
54
+ OpenAppendable ( name string ) ( WritableFile , error )
59
55
}
60
56
61
- func (fsys MkdirFsImpl ) MkdirAll (path string , perm fs.FileMode ) error {
62
- return os .MkdirAll (fsys .dir + "/" + path , perm )
57
+ type readWriteFSImpl struct {
63
58
}
64
59
65
- func (fsys MkdirFsImpl ) Open (name string ) (fs.File , error ) {
66
- return os .OpenFile ( fsys . dir + "/" + name , os . O_CREATE | os . O_RDWR | os . O_TRUNC , 0644 )
60
+ func (fwfs readWriteFSImpl ) Open (name string ) (fs.File , error ) {
61
+ return os .Open ( name )
67
62
}
68
63
69
- func (fsys MkdirFsImpl ) OpenAtEnd (name string ) (fs.File , error ) {
70
- file , err := os .OpenFile (fsys .dir + "/" + name , os .O_CREATE | os .O_RDWR , 0644 )
64
+ func (fwfs readWriteFSImpl ) OpenWritable (name string ) (WritableFile , error ) {
65
+ if err := os .MkdirAll (filepath .Dir (name ), os .ModePerm ); err != nil {
66
+ return nil , err
67
+ }
68
+ return os .OpenFile (name , os .O_CREATE | os .O_RDWR | os .O_TRUNC , 0644 )
69
+ }
70
+
71
+ func (fwfs readWriteFSImpl ) OpenAppendable (name string ) (WritableFile , error ) {
72
+ if err := os .MkdirAll (filepath .Dir (name ), os .ModePerm ); err != nil {
73
+ return nil , err
74
+ }
75
+ file , err := os .OpenFile (name , os .O_CREATE | os .O_RDWR , 0644 )
71
76
72
77
if err != nil {
73
78
return nil , err
@@ -77,13 +82,16 @@ func (fsys MkdirFsImpl) OpenAtEnd(name string) (fs.File, error) {
77
82
if err != nil {
78
83
return nil , err
79
84
}
80
-
81
85
return file , nil
82
86
}
83
87
84
88
var gzipExtension = ".gz__"
85
89
86
- func uploads (router * httprouter.Router , fsys MkdirFS ) {
90
+ func safeResolve (baseDir string , relPath string ) string {
91
+ return filepath .Join (baseDir , filepath .Clean (filepath .Join (string (os .PathSeparator ), relPath )))
92
+ }
93
+
94
+ func uploads (router * httprouter.Router , baseDir string , fsys WriteFS ) {
87
95
router .POST ("/_apis/pipelines/workflows/:runId/artifacts" , func (w http.ResponseWriter , req * http.Request , params httprouter.Params ) {
88
96
runID := params .ByName ("runId" )
89
97
@@ -108,19 +116,15 @@ func uploads(router *httprouter.Router, fsys MkdirFS) {
108
116
itemPath += gzipExtension
109
117
}
110
118
111
- filePath := fmt .Sprintf ("%s/%s" , runID , itemPath )
119
+ safeRunPath := safeResolve (baseDir , runID )
120
+ safePath := safeResolve (safeRunPath , itemPath )
112
121
113
- err := fsys .MkdirAll (path .Dir (filePath ), os .ModePerm )
114
- if err != nil {
115
- panic (err )
116
- }
117
-
118
- file , err := func () (fs.File , error ) {
122
+ file , err := func () (WritableFile , error ) {
119
123
contentRange := req .Header .Get ("Content-Range" )
120
124
if contentRange != "" && ! strings .HasPrefix (contentRange , "bytes 0-" ) {
121
- return fsys .OpenAtEnd ( filePath )
125
+ return fsys .OpenAppendable ( safePath )
122
126
}
123
- return fsys .Open ( filePath )
127
+ return fsys .OpenWritable ( safePath )
124
128
}()
125
129
126
130
if err != nil {
@@ -170,11 +174,13 @@ func uploads(router *httprouter.Router, fsys MkdirFS) {
170
174
})
171
175
}
172
176
173
- func downloads (router * httprouter.Router , fsys fs.FS ) {
177
+ func downloads (router * httprouter.Router , baseDir string , fsys fs.FS ) {
174
178
router .GET ("/_apis/pipelines/workflows/:runId/artifacts" , func (w http.ResponseWriter , req * http.Request , params httprouter.Params ) {
175
179
runID := params .ByName ("runId" )
176
180
177
- entries , err := fs .ReadDir (fsys , runID )
181
+ safePath := safeResolve (baseDir , runID )
182
+
183
+ entries , err := fs .ReadDir (fsys , safePath )
178
184
if err != nil {
179
185
panic (err )
180
186
}
@@ -204,12 +210,12 @@ func downloads(router *httprouter.Router, fsys fs.FS) {
204
210
router .GET ("/download/:container" , func (w http.ResponseWriter , req * http.Request , params httprouter.Params ) {
205
211
container := params .ByName ("container" )
206
212
itemPath := req .URL .Query ().Get ("itemPath" )
207
- dirPath := fmt . Sprintf ( "%s/%s" , container , itemPath )
213
+ safePath := safeResolve ( baseDir , filepath . Join ( container , itemPath ) )
208
214
209
215
var files []ContainerItem
210
- err := fs .WalkDir (fsys , dirPath , func (path string , entry fs.DirEntry , err error ) error {
216
+ err := fs .WalkDir (fsys , safePath , func (path string , entry fs.DirEntry , err error ) error {
211
217
if ! entry .IsDir () {
212
- rel , err := filepath .Rel (dirPath , path )
218
+ rel , err := filepath .Rel (safePath , path )
213
219
if err != nil {
214
220
panic (err )
215
221
}
@@ -218,7 +224,7 @@ func downloads(router *httprouter.Router, fsys fs.FS) {
218
224
rel = strings .TrimSuffix (rel , gzipExtension )
219
225
220
226
files = append (files , ContainerItem {
221
- Path : fmt . Sprintf ( "%s/%s" , itemPath , rel ),
227
+ Path : filepath . Join ( itemPath , rel ),
222
228
ItemType : "file" ,
223
229
ContentLocation : fmt .Sprintf ("http://%s/artifact/%s/%s/%s" , req .Host , container , itemPath , rel ),
224
230
})
@@ -245,10 +251,12 @@ func downloads(router *httprouter.Router, fsys fs.FS) {
245
251
router .GET ("/artifact/*path" , func (w http.ResponseWriter , req * http.Request , params httprouter.Params ) {
246
252
path := params .ByName ("path" )[1 :]
247
253
248
- file , err := fsys .Open (path )
254
+ safePath := safeResolve (baseDir , path )
255
+
256
+ file , err := fsys .Open (safePath )
249
257
if err != nil {
250
258
// try gzip file
251
- file , err = fsys .Open (path + gzipExtension )
259
+ file , err = fsys .Open (safePath + gzipExtension )
252
260
if err != nil {
253
261
panic (err )
254
262
}
@@ -273,9 +281,9 @@ func Serve(ctx context.Context, artifactPath string, addr string, port string) c
273
281
router := httprouter .New ()
274
282
275
283
logger .Debugf ("Artifacts base path '%s'" , artifactPath )
276
- fs := os . DirFS ( artifactPath )
277
- uploads (router , MkdirFsImpl { artifactPath , fs } )
278
- downloads (router , fs )
284
+ fsys := readWriteFSImpl {}
285
+ uploads (router , artifactPath , fsys )
286
+ downloads (router , artifactPath , fsys )
279
287
280
288
server := & http.Server {
281
289
Addr : fmt .Sprintf ("%s:%s" , addr , port ),
0 commit comments