88 "io"
99 "os"
1010 "path/filepath"
11+ "strings"
1112 "time"
1213)
1314
@@ -31,12 +32,19 @@ func NewFilesystem(root string) (*Filesystem, error) {
3132 return & Filesystem {root : absRoot }, nil
3233}
3334
34- func (fs * Filesystem ) fullPath (path string ) string {
35- return filepath .Join (fs .root , filepath .FromSlash (path ))
35+ func (fs * Filesystem ) fullPath (path string ) (string , error ) {
36+ full := filepath .Clean (filepath .Join (fs .root , filepath .FromSlash (path )))
37+ if full != fs .root && ! strings .HasPrefix (full , fs .root + string (filepath .Separator )) {
38+ return "" , fmt .Errorf ("%w: path escapes storage root" , ErrNotFound )
39+ }
40+ return full , nil
3641}
3742
3843func (fs * Filesystem ) Store (ctx context.Context , path string , r io.Reader ) (int64 , string , error ) {
39- fullPath := fs .fullPath (path )
44+ fullPath , err := fs .fullPath (path )
45+ if err != nil {
46+ return 0 , "" , err
47+ }
4048
4149 dir := filepath .Dir (fullPath )
4250 if err := os .MkdirAll (dir , dirPermissions ); err != nil {
@@ -83,7 +91,10 @@ func (fs *Filesystem) Store(ctx context.Context, path string, r io.Reader) (int6
8391}
8492
8593func (fs * Filesystem ) Open (ctx context.Context , path string ) (io.ReadCloser , error ) {
86- fullPath := fs .fullPath (path )
94+ fullPath , err := fs .fullPath (path )
95+ if err != nil {
96+ return nil , err
97+ }
8798
8899 f , err := os .Open (fullPath )
89100 if err != nil {
@@ -97,9 +108,12 @@ func (fs *Filesystem) Open(ctx context.Context, path string) (io.ReadCloser, err
97108}
98109
99110func (fs * Filesystem ) Exists (ctx context.Context , path string ) (bool , error ) {
100- fullPath := fs .fullPath (path )
111+ fullPath , err := fs .fullPath (path )
112+ if err != nil {
113+ return false , err
114+ }
101115
102- _ , err : = os .Stat (fullPath )
116+ _ , err = os .Stat (fullPath )
103117 if err != nil {
104118 if os .IsNotExist (err ) {
105119 return false , nil
@@ -111,9 +125,12 @@ func (fs *Filesystem) Exists(ctx context.Context, path string) (bool, error) {
111125}
112126
113127func (fs * Filesystem ) Delete (ctx context.Context , path string ) error {
114- fullPath := fs .fullPath (path )
128+ fullPath , err := fs .fullPath (path )
129+ if err != nil {
130+ return err
131+ }
115132
116- err : = os .Remove (fullPath )
133+ err = os .Remove (fullPath )
117134 if err != nil && ! os .IsNotExist (err ) {
118135 return fmt .Errorf ("removing file: %w" , err )
119136 }
@@ -135,7 +152,10 @@ func (fs *Filesystem) SignedURL(_ context.Context, _ string, _ time.Duration) (s
135152}
136153
137154func (fs * Filesystem ) Size (ctx context.Context , path string ) (int64 , error ) {
138- fullPath := fs .fullPath (path )
155+ fullPath , err := fs .fullPath (path )
156+ if err != nil {
157+ return 0 , err
158+ }
139159
140160 info , err := os .Stat (fullPath )
141161 if err != nil {
@@ -174,7 +194,8 @@ func (fs *Filesystem) Root() string {
174194
175195// FullPath returns the full filesystem path for a storage path.
176196// Useful for serving files directly or debugging.
177- func (fs * Filesystem ) FullPath (path string ) string {
197+ // Returns an error if the resulting path would escape the storage root.
198+ func (fs * Filesystem ) FullPath (path string ) (string , error ) {
178199 return fs .fullPath (path )
179200}
180201
0 commit comments