1
+ /*
2
+ Given a path to a folder on the filesystem, this provides
3
+ a wrapper class with an API very similar to the fs/promises modules,
4
+ but which only allows access to files in that folder.
5
+ */
6
+
1
7
import {
2
8
appendFile ,
3
9
chmod ,
@@ -21,111 +27,100 @@ import {
21
27
import { exists } from "@cocalc/backend/misc/async-utils-node" ;
22
28
import { type DirectoryListingEntry } from "@cocalc/util/types" ;
23
29
import getListing from "@cocalc/backend/get-listing" ;
24
- import { type Subvolume } from "./subvolume" ;
25
- import { isdir , sudo } from "./util" ;
26
-
27
- export class SubvolumeFilesystem {
28
- constructor ( private subvolume : Subvolume ) { }
29
-
30
- private normalize = this . subvolume . normalize ;
30
+ import { isdir , sudo } from "../btrfs/util" ;
31
+ import { join , resolve } from "path" ;
31
32
32
- ls = async (
33
- path : string ,
34
- { hidden, limit } : { hidden ?: boolean ; limit ?: number } = { } ,
35
- ) : Promise < DirectoryListingEntry [ ] > => {
36
- return await getListing ( this . normalize ( path ) , hidden , {
37
- limit,
38
- home : "/" ,
39
- } ) ;
40
- } ;
33
+ export class SandboxedFilesystem {
34
+ // path should be the path to a FOLDER on the filesystem (not a file)
35
+ constructor ( public readonly path : string ) { }
41
36
42
- readFile = async ( path : string , encoding ?: any ) : Promise < string | Buffer > => {
43
- return await readFile ( this . normalize ( path ) , encoding ) ;
37
+ private safeAbsPath = ( path : string ) => {
38
+ if ( typeof path != "string" ) {
39
+ throw Error ( `path must be a string but is of type ${ typeof path } ` ) ;
40
+ }
41
+ return join ( this . path , resolve ( "/" , path ) ) ;
44
42
} ;
45
43
46
- writeFile = async ( path : string , data : string | Buffer ) => {
47
- return await writeFile ( this . normalize ( path ) , data ) ;
44
+ appendFile = async ( path : string , data : string | Buffer , encoding ? ) => {
45
+ return await appendFile ( this . safeAbsPath ( path ) , data , encoding ) ;
48
46
} ;
49
47
50
- appendFile = async ( path : string , data : string | Buffer , encoding ? ) => {
51
- return await appendFile ( this . normalize ( path ) , data , encoding ) ;
48
+ chmod = async ( path : string , mode : string | number ) => {
49
+ await chmod ( this . safeAbsPath ( path ) , mode ) ;
52
50
} ;
53
51
54
- unlink = async ( path : string ) => {
55
- await unlink ( this . normalize ( path ) ) ;
52
+ copyFile = async ( src : string , dest : string ) => {
53
+ await copyFile ( this . safeAbsPath ( src ) , this . safeAbsPath ( dest ) ) ;
56
54
} ;
57
55
58
- stat = async ( path : string ) => {
59
- return await stat ( this . normalize ( path ) ) ;
56
+ cp = async ( src : string , dest : string , options ? ) => {
57
+ await cp ( this . safeAbsPath ( src ) , this . safeAbsPath ( dest ) , options ) ;
60
58
} ;
61
59
62
60
exists = async ( path : string ) => {
63
- return await exists ( this . normalize ( path ) ) ;
61
+ return await exists ( this . safeAbsPath ( path ) ) ;
64
62
} ;
65
63
66
64
// hard link
67
65
link = async ( existingPath : string , newPath : string ) => {
68
- return await link ( this . normalize ( existingPath ) , this . normalize ( newPath ) ) ;
69
- } ;
70
-
71
- symlink = async ( target : string , path : string ) => {
72
- return await symlink ( this . normalize ( target ) , this . normalize ( path ) ) ;
73
- } ;
74
-
75
- realpath = async ( path : string ) => {
76
- const x = await realpath ( this . normalize ( path ) ) ;
77
- return x . slice ( this . subvolume . path . length + 1 ) ;
78
- } ;
79
-
80
- rename = async ( oldPath : string , newPath : string ) => {
81
- await rename ( this . normalize ( oldPath ) , this . normalize ( newPath ) ) ;
66
+ return await link (
67
+ this . safeAbsPath ( existingPath ) ,
68
+ this . safeAbsPath ( newPath ) ,
69
+ ) ;
82
70
} ;
83
71
84
- utimes = async (
72
+ ls = async (
85
73
path : string ,
86
- atime : number | string | Date ,
87
- mtime : number | string | Date ,
88
- ) => {
89
- await utimes ( this . normalize ( path ) , atime , mtime ) ;
74
+ { hidden, limit } : { hidden ?: boolean ; limit ?: number } = { } ,
75
+ ) : Promise < DirectoryListingEntry [ ] > => {
76
+ return await getListing ( this . safeAbsPath ( path ) , hidden , {
77
+ limit,
78
+ home : "/" ,
79
+ } ) ;
90
80
} ;
91
81
92
- watch = ( filename : string , options ?) => {
93
- return watch ( this . normalize ( filename ) , options ) ;
82
+ mkdir = async ( path : string , options ?) => {
83
+ await mkdir ( this . safeAbsPath ( path ) , options ) ;
94
84
} ;
95
85
96
- truncate = async ( path : string , len ?: number ) => {
97
- await truncate ( this . normalize ( path ) , len ) ;
86
+ readFile = async ( path : string , encoding ?: any ) : Promise < string | Buffer > => {
87
+ return await readFile ( this . safeAbsPath ( path ) , encoding ) ;
98
88
} ;
99
89
100
- copyFile = async ( src : string , dest : string ) => {
101
- await copyFile ( this . normalize ( src ) , this . normalize ( dest ) ) ;
90
+ realpath = async ( path : string ) => {
91
+ const x = await realpath ( this . safeAbsPath ( path ) ) ;
92
+ return x . slice ( this . path . length + 1 ) ;
102
93
} ;
103
94
104
- cp = async ( src : string , dest : string , options ? ) => {
105
- await cp ( this . normalize ( src ) , this . normalize ( dest ) , options ) ;
95
+ rename = async ( oldPath : string , newPath : string ) => {
96
+ await rename ( this . safeAbsPath ( oldPath ) , this . safeAbsPath ( newPath ) ) ;
106
97
} ;
107
98
108
- chmod = async ( path : string , mode : string | number ) => {
109
- await chmod ( this . normalize ( path ) , mode ) ;
99
+ rm = async ( path : string , options ? ) => {
100
+ await rm ( this . safeAbsPath ( path ) , options ) ;
110
101
} ;
111
102
112
- mkdir = async ( path : string , options ?) => {
113
- await mkdir ( this . normalize ( path ) , options ) ;
103
+ rmdir = async ( path : string , options ?) => {
104
+ await rmdir ( this . safeAbsPath ( path ) , options ) ;
114
105
} ;
115
106
116
107
rsync = async ( {
117
108
src,
118
109
target,
119
- args = [ "-axH" ] ,
120
110
timeout = 5 * 60 * 1000 ,
121
111
} : {
122
112
src : string ;
123
113
target : string ;
124
- args ?: string [ ] ;
125
114
timeout ?: number ;
126
115
} ) : Promise < { stdout : string ; stderr : string ; exit_code : number } > => {
127
- let srcPath = this . normalize ( src ) ;
128
- let targetPath = this . normalize ( target ) ;
116
+ let srcPath = this . safeAbsPath ( src ) ;
117
+ let targetPath = this . safeAbsPath ( target ) ;
118
+ if ( src . endsWith ( "/" ) ) {
119
+ srcPath += "/" ;
120
+ }
121
+ if ( target . endsWith ( "/" ) ) {
122
+ targetPath += "/" ;
123
+ }
129
124
if ( ! srcPath . endsWith ( "/" ) && ( await isdir ( srcPath ) ) ) {
130
125
srcPath += "/" ;
131
126
if ( ! targetPath . endsWith ( "/" ) ) {
@@ -134,17 +129,41 @@ export class SubvolumeFilesystem {
134
129
}
135
130
return await sudo ( {
136
131
command : "rsync" ,
137
- args : [ ... args , srcPath , targetPath ] ,
132
+ args : [ srcPath , targetPath ] ,
138
133
err_on_exit : false ,
139
134
timeout : timeout / 1000 ,
140
135
} ) ;
141
136
} ;
142
137
143
- rmdir = async ( path : string , options ? ) => {
144
- await rmdir ( this . normalize ( path ) , options ) ;
138
+ stat = async ( path : string ) => {
139
+ return await stat ( this . safeAbsPath ( path ) ) ;
145
140
} ;
146
141
147
- rm = async ( path : string , options ?) => {
148
- await rm ( this . normalize ( path ) , options ) ;
142
+ symlink = async ( target : string , path : string ) => {
143
+ return await symlink ( this . safeAbsPath ( target ) , this . safeAbsPath ( path ) ) ;
144
+ } ;
145
+
146
+ truncate = async ( path : string , len ?: number ) => {
147
+ await truncate ( this . safeAbsPath ( path ) , len ) ;
148
+ } ;
149
+
150
+ unlink = async ( path : string ) => {
151
+ await unlink ( this . safeAbsPath ( path ) ) ;
152
+ } ;
153
+
154
+ utimes = async (
155
+ path : string ,
156
+ atime : number | string | Date ,
157
+ mtime : number | string | Date ,
158
+ ) => {
159
+ await utimes ( this . safeAbsPath ( path ) , atime , mtime ) ;
160
+ } ;
161
+
162
+ watch = ( filename : string , options ?) => {
163
+ return watch ( this . safeAbsPath ( filename ) , options ) ;
164
+ } ;
165
+
166
+ writeFile = async ( path : string , data : string | Buffer ) => {
167
+ return await writeFile ( this . safeAbsPath ( path ) , data ) ;
149
168
} ;
150
169
}
0 commit comments