-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into 585-implement-tests-for-the-http-package
- Loading branch information
Showing
19 changed files
with
443 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
|
||
import InvalidLocation from './errors/InvalidLocation'; | ||
import FileNotFound from './errors/FileNotFound'; | ||
|
||
import FileSystem from './interfaces/FileSystem'; | ||
|
||
import File from './models/File'; | ||
|
||
import LocalFileSystem from './LocalFileSystem'; | ||
|
||
const DEFAULT_MIME_TYPE = 'application/octet-stream'; | ||
|
||
export default class FileManager | ||
{ | ||
readonly #location: string; | ||
readonly #rootLocation: string; | ||
readonly #fileSystem: FileSystem; | ||
|
||
constructor(location: string, fileSystem = new LocalFileSystem()) | ||
{ | ||
this.#location = location; | ||
this.#fileSystem = fileSystem; | ||
this.#rootLocation = fileSystem.resolve(location); | ||
} | ||
|
||
// This method must be used by every function that needs to access | ||
// the file system. This ensures that the path is always validated | ||
// and prevents access to files outside of the base location. | ||
getAbsoluteLocation(filename: string): string | ||
{ | ||
const location = filename.startsWith('/') ? filename : this.#fileSystem.join(this.#location, filename); | ||
const absolutePath = this.#fileSystem.resolve(location); | ||
|
||
this.#validateLocation(absolutePath, filename); | ||
|
||
return absolutePath; | ||
} | ||
|
||
getRelativeLocation(filename: string): string | ||
{ | ||
return this.#fileSystem.relative(this.#location, filename); | ||
} | ||
|
||
async getType(filename: string): Promise<string> | ||
{ | ||
const location = this.getAbsoluteLocation(filename); | ||
const type = await this.#fileSystem.mimeType(location); | ||
|
||
return type ?? DEFAULT_MIME_TYPE; | ||
} | ||
|
||
async getContent(filename: string): Promise<Buffer> | ||
{ | ||
const location = this.getAbsoluteLocation(filename); | ||
const exists = await this.#fileSystem.exists(location); | ||
|
||
if (exists === false) | ||
{ | ||
// Do NOT use the location in the error message, | ||
// as it may contain sensitive information. | ||
throw new FileNotFound(filename); | ||
} | ||
|
||
return this.#fileSystem.read(location); | ||
} | ||
|
||
async exists(filename: string): Promise<boolean> | ||
{ | ||
const location = this.getAbsoluteLocation(filename); | ||
|
||
return this.#fileSystem.exists(location); | ||
} | ||
|
||
async read(filename: string): Promise<File> | ||
{ | ||
const absoluteFilename = this.getAbsoluteLocation(filename); | ||
|
||
const type = await this.getType(absoluteFilename); | ||
const content = await this.getContent(absoluteFilename); | ||
|
||
return new File(filename, type, content); | ||
} | ||
|
||
async write(filename: string, content: string): Promise<void> | ||
{ | ||
const location = this.getAbsoluteLocation(filename); | ||
|
||
return this.#fileSystem.write(location, content); | ||
} | ||
|
||
async copy(source: string, destination: string): Promise<void> | ||
{ | ||
const sourceLocation = this.getAbsoluteLocation(source); | ||
const destinationLocation = this.getAbsoluteLocation(destination); | ||
|
||
return this.#fileSystem.copy(sourceLocation, destinationLocation); | ||
} | ||
|
||
async delete(filename: string): Promise<void> | ||
{ | ||
const location = this.getAbsoluteLocation(filename); | ||
|
||
return this.#fileSystem.delete(location); | ||
} | ||
|
||
async filter(pattern: string): Promise<string[]> | ||
{ | ||
const location = this.getAbsoluteLocation('./'); | ||
|
||
return this.#fileSystem.filter(location, pattern); | ||
} | ||
|
||
#validateLocation(location: string, filename: string): void | ||
{ | ||
if (location.startsWith(this.#rootLocation) === false) | ||
{ | ||
// The filename is only needed for the error message. This | ||
// ensures that the error message does not contain sensitive | ||
// information. | ||
throw new InvalidLocation(filename); | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
|
||
import fs from 'fs-extra'; | ||
import { glob } from 'glob'; | ||
import mime from 'mime-types'; | ||
import path from 'path'; | ||
|
||
import FileSystem from './interfaces/FileSystem'; | ||
|
||
export default class LocalFileSystem implements FileSystem | ||
{ | ||
copy(source: string, destination: string): Promise<void> | ||
{ | ||
return fs.copy(source, destination, { overwrite: true }); | ||
} | ||
|
||
delete(location: string): Promise<void> | ||
{ | ||
return fs.remove(location); | ||
} | ||
|
||
exists(location: string): Promise<boolean> | ||
{ | ||
return fs.exists(location); | ||
} | ||
|
||
filter(location: string, pattern: string): Promise<string[]> | ||
{ | ||
return glob(`${location}/${pattern}`); | ||
} | ||
|
||
join(...paths: string[]): string | ||
{ | ||
return path.join(...paths); | ||
} | ||
|
||
read(location: string): Promise<Buffer> | ||
{ | ||
return fs.readFile(location); | ||
} | ||
|
||
resolve(location: string): string | ||
{ | ||
return path.resolve(location); | ||
} | ||
|
||
relative(from: string, to: string): string | ||
{ | ||
return path.relative(from, to); | ||
} | ||
|
||
async mimeType(location: string): Promise<string | undefined> | ||
{ | ||
return mime.lookup(location) || undefined; | ||
} | ||
|
||
async write(location: string, content: string): Promise<void> | ||
{ | ||
const directory = path.dirname(location); | ||
|
||
fs.mkdirSync(directory, { recursive: true }); | ||
|
||
return fs.writeFile(location, content); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
|
||
export default class InvalidPath extends Error | ||
{ | ||
readonly #location: string; | ||
|
||
constructor(location: string) | ||
{ | ||
super(`Invalid location: ${location}`); | ||
|
||
this.#location = location; | ||
} | ||
|
||
get location() { return this.#location; } | ||
} |
Oops, something went wrong.