@@ -11,23 +11,39 @@ import (
11
11
"strings"
12
12
13
13
"github.com/shirou/gopsutil/v4/disk"
14
+ "github.com/spf13/afero"
14
15
"golang.org/x/xerrors"
15
16
16
17
"github.com/coder/coder/v2/coderd/httpapi"
17
18
"github.com/coder/coder/v2/codersdk"
19
+ "github.com/coder/coder/v2/codersdk/workspacesdk"
18
20
)
19
21
20
22
var WindowsDriveRegex = regexp .MustCompile (`^[a-zA-Z]:\\$` )
21
23
22
- func (* agent ) HandleLS (rw http.ResponseWriter , r * http.Request ) {
24
+ func (a * agent ) HandleLS (rw http.ResponseWriter , r * http.Request ) {
23
25
ctx := r .Context ()
24
26
25
- var query LSRequest
26
- if ! httpapi .Read (ctx , rw , r , & query ) {
27
+ // An absolute path may be optionally provided, otherwise a path split into an
28
+ // array must be provided in the body (which can be relative).
29
+ query := r .URL .Query ()
30
+ parser := httpapi .NewQueryParamParser ()
31
+ path := parser .String (query , "" , "path" )
32
+ parser .ErrorExcessParams (query )
33
+ if len (parser .Errors ) > 0 {
34
+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
35
+ Message : "Query parameters have invalid values." ,
36
+ Validations : parser .Errors ,
37
+ })
27
38
return
28
39
}
29
40
30
- resp , err := listFiles (query )
41
+ var req workspacesdk.LSRequest
42
+ if ! httpapi .Read (ctx , rw , r , & req ) {
43
+ return
44
+ }
45
+
46
+ resp , err := listFiles (a .filesystem , path , req )
31
47
if err != nil {
32
48
status := http .StatusInternalServerError
33
49
switch {
@@ -46,66 +62,74 @@ func (*agent) HandleLS(rw http.ResponseWriter, r *http.Request) {
46
62
httpapi .Write (ctx , rw , http .StatusOK , resp )
47
63
}
48
64
49
- func listFiles (query LSRequest ) (LSResponse , error ) {
50
- var fullPath []string
51
- switch query .Relativity {
52
- case LSRelativityHome :
53
- home , err := os .UserHomeDir ()
54
- if err != nil {
55
- return LSResponse {}, xerrors .Errorf ("failed to get user home directory: %w" , err )
65
+ func listFiles (fs afero.Fs , path string , query workspacesdk.LSRequest ) (workspacesdk.LSResponse , error ) {
66
+ absolutePathString := path
67
+ if absolutePathString != "" {
68
+ if ! filepath .IsAbs (path ) {
69
+ return workspacesdk.LSResponse {}, xerrors .Errorf ("path must be absolute: %q" , path )
56
70
}
57
- fullPath = []string {home }
58
- case LSRelativityRoot :
59
- if runtime .GOOS == "windows" {
60
- if len (query .Path ) == 0 {
61
- return listDrives ()
71
+ } else {
72
+ var fullPath []string
73
+ switch query .Relativity {
74
+ case workspacesdk .LSRelativityHome :
75
+ home , err := os .UserHomeDir ()
76
+ if err != nil {
77
+ return workspacesdk.LSResponse {}, xerrors .Errorf ("failed to get user home directory: %w" , err )
62
78
}
63
- if ! WindowsDriveRegex .MatchString (query .Path [0 ]) {
64
- return LSResponse {}, xerrors .Errorf ("invalid drive letter %q" , query .Path [0 ])
79
+ fullPath = []string {home }
80
+ case workspacesdk .LSRelativityRoot :
81
+ if runtime .GOOS == "windows" {
82
+ if len (query .Path ) == 0 {
83
+ return listDrives ()
84
+ }
85
+ if ! WindowsDriveRegex .MatchString (query .Path [0 ]) {
86
+ return workspacesdk.LSResponse {}, xerrors .Errorf ("invalid drive letter %q" , query .Path [0 ])
87
+ }
88
+ } else {
89
+ fullPath = []string {"/" }
65
90
}
66
- } else {
67
- fullPath = [] string { "/" }
91
+ default :
92
+ return workspacesdk. LSResponse {}, xerrors . Errorf ( "unsupported relativity type %q" , query . Relativity )
68
93
}
69
- default :
70
- return LSResponse {}, xerrors .Errorf ("unsupported relativity type %q" , query .Relativity )
71
- }
72
94
73
- fullPath = append (fullPath , query .Path ... )
74
- fullPathRelative := filepath .Join (fullPath ... )
75
- absolutePathString , err := filepath .Abs (fullPathRelative )
76
- if err != nil {
77
- return LSResponse {}, xerrors .Errorf ("failed to get absolute path of %q: %w" , fullPathRelative , err )
95
+ fullPath = append (fullPath , query .Path ... )
96
+ fullPathRelative := filepath .Join (fullPath ... )
97
+ var err error
98
+ absolutePathString , err = filepath .Abs (fullPathRelative )
99
+ if err != nil {
100
+ return workspacesdk.LSResponse {}, xerrors .Errorf ("failed to get absolute path of %q: %w" , fullPathRelative , err )
101
+ }
78
102
}
79
103
80
104
// codeql[go/path-injection] - The intent is to allow the user to navigate to any directory in their workspace.
81
- f , err := os .Open (absolutePathString )
105
+ f , err := fs .Open (absolutePathString )
82
106
if err != nil {
83
- return LSResponse {}, xerrors .Errorf ("failed to open directory %q: %w" , absolutePathString , err )
107
+ return workspacesdk. LSResponse {}, xerrors .Errorf ("failed to open directory %q: %w" , absolutePathString , err )
84
108
}
85
109
defer f .Close ()
86
110
87
111
stat , err := f .Stat ()
88
112
if err != nil {
89
- return LSResponse {}, xerrors .Errorf ("failed to stat directory %q: %w" , absolutePathString , err )
113
+ return workspacesdk. LSResponse {}, xerrors .Errorf ("failed to stat directory %q: %w" , absolutePathString , err )
90
114
}
91
115
92
116
if ! stat .IsDir () {
93
- return LSResponse {}, xerrors .Errorf ("path %q is not a directory" , absolutePathString )
117
+ return workspacesdk. LSResponse {}, xerrors .Errorf ("path %q is not a directory" , absolutePathString )
94
118
}
95
119
96
120
// `contents` may be partially populated even if the operation fails midway.
97
- contents , _ := f .ReadDir (- 1 )
98
- respContents := make ([]LSFile , 0 , len (contents ))
121
+ contents , _ := f .Readdir (- 1 )
122
+ respContents := make ([]workspacesdk. LSFile , 0 , len (contents ))
99
123
for _ , file := range contents {
100
- respContents = append (respContents , LSFile {
124
+ respContents = append (respContents , workspacesdk. LSFile {
101
125
Name : file .Name (),
102
126
AbsolutePathString : filepath .Join (absolutePathString , file .Name ()),
103
127
IsDir : file .IsDir (),
104
128
})
105
129
}
106
130
107
131
// Sort alphabetically: directories then files
108
- slices .SortFunc (respContents , func (a , b LSFile ) int {
132
+ slices .SortFunc (respContents , func (a , b workspacesdk. LSFile ) int {
109
133
if a .IsDir && ! b .IsDir {
110
134
return - 1
111
135
}
@@ -117,35 +141,35 @@ func listFiles(query LSRequest) (LSResponse, error) {
117
141
118
142
absolutePath := pathToArray (absolutePathString )
119
143
120
- return LSResponse {
144
+ return workspacesdk. LSResponse {
121
145
AbsolutePath : absolutePath ,
122
146
AbsolutePathString : absolutePathString ,
123
147
Contents : respContents ,
124
148
}, nil
125
149
}
126
150
127
- func listDrives () (LSResponse , error ) {
151
+ func listDrives () (workspacesdk. LSResponse , error ) {
128
152
// disk.Partitions() will return partitions even if there was a failure to
129
153
// get one. Any errored partitions will not be returned.
130
154
partitionStats , err := disk .Partitions (true )
131
155
if err != nil && len (partitionStats ) == 0 {
132
156
// Only return the error if there were no partitions returned.
133
- return LSResponse {}, xerrors .Errorf ("failed to get partitions: %w" , err )
157
+ return workspacesdk. LSResponse {}, xerrors .Errorf ("failed to get partitions: %w" , err )
134
158
}
135
159
136
- contents := make ([]LSFile , 0 , len (partitionStats ))
160
+ contents := make ([]workspacesdk. LSFile , 0 , len (partitionStats ))
137
161
for _ , a := range partitionStats {
138
162
// Drive letters on Windows have a trailing separator as part of their name.
139
163
// i.e. `os.Open("C:")` does not work, but `os.Open("C:\\")` does.
140
164
name := a .Mountpoint + string (os .PathSeparator )
141
- contents = append (contents , LSFile {
165
+ contents = append (contents , workspacesdk. LSFile {
142
166
Name : name ,
143
167
AbsolutePathString : name ,
144
168
IsDir : true ,
145
169
})
146
170
}
147
171
148
- return LSResponse {
172
+ return workspacesdk. LSResponse {
149
173
AbsolutePath : []string {},
150
174
AbsolutePathString : "" ,
151
175
Contents : contents ,
@@ -163,36 +187,3 @@ func pathToArray(path string) []string {
163
187
}
164
188
return out
165
189
}
166
-
167
- type LSRequest struct {
168
- // e.g. [], ["repos", "coder"],
169
- Path []string `json:"path"`
170
- // Whether the supplied path is relative to the user's home directory,
171
- // or the root directory.
172
- Relativity LSRelativity `json:"relativity"`
173
- }
174
-
175
- type LSResponse struct {
176
- AbsolutePath []string `json:"absolute_path"`
177
- // Returned so clients can display the full path to the user, and
178
- // copy it to configure file sync
179
- // e.g. Windows: "C:\\Users\\coder"
180
- // Linux: "/home/coder"
181
- AbsolutePathString string `json:"absolute_path_string"`
182
- Contents []LSFile `json:"contents"`
183
- }
184
-
185
- type LSFile struct {
186
- Name string `json:"name"`
187
- // e.g. "C:\\Users\\coder\\hello.txt"
188
- // "/home/coder/hello.txt"
189
- AbsolutePathString string `json:"absolute_path_string"`
190
- IsDir bool `json:"is_dir"`
191
- }
192
-
193
- type LSRelativity string
194
-
195
- const (
196
- LSRelativityRoot LSRelativity = "root"
197
- LSRelativityHome LSRelativity = "home"
198
- )
0 commit comments