This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+51
View File
@@ -0,0 +1,51 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { DirectoryListingContract, DriveListItem, DriverContract } from '@ioc:Adonis/Core/Drive';
/**
* Directory listing exposes the API to list directory contents using async iterators
* and also adds some helper functions for transforming the output of driver list.
*/
export declare class DirectoryListing<Driver extends DriverContract, T extends DriveListItem> implements DirectoryListingContract<Driver, T> {
driver: Driver;
private listing;
/**
* Functions chain to be executed for transforming generated listing iterable
*/
private chain;
constructor(driver: Driver, listing: (this: Driver) => AsyncGenerator<T>);
/**
* Filter generated items of listing with the given predicate function.
*/
filter(predicate: (item: T, index: number, driver: Driver) => Promise<boolean> | boolean): DirectoryListingContract<Driver, T>;
/**
* Transform generated items of listing with the given mapper function.
*/
map<M>(mapper: (item: T, index: number, driver: Driver) => M | Promise<M>): DirectoryListingContract<Driver, Awaited<M>>;
/**
* Do recursive listing of items. Without the next function it will do listing of leaf nodes only.
* For advanced usage you can pass the next function which will get as parameter current item and it should
* return the next location for list or null if the recursion should stop and yield the current item.
* For advanced usage you can also limit the depth of recursion using the second argument of next function.
*/
recursive(next?: (current: T, depth: number, driver: Driver) => Promise<string | null> | string | null): DirectoryListingContract<Driver, any>;
/**
* Add a piping chain function which gets the current async iterable and returns
* new async iterable with modified directory listing output.
* Function this is bound to instance of driver for which the listing is generated.
* This allows using async generator functions and reference the driver methods easily.
* Piping will always return clone of the current instance and add the function
* to the chain of new cloned instance only to prevent side effects.
*/
pipe<U>(fn: (this: Driver, iterable: AsyncIterable<T>) => AsyncIterable<U>): DirectoryListingContract<Driver, U>;
/**
* Get the final async iterable after passing directory listing through chain of piping functions modifying the output.
*/
toIterable(): AsyncIterable<T>;
/**
* Convert directory listing to array.
*/
toArray(): Promise<T[]>;
/**
* A method that returns the default async iterator for an object.
*/
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
}
+129
View File
@@ -0,0 +1,129 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DirectoryListing = void 0;
/**
* Directory listing exposes the API to list directory contents using async iterators
* and also adds some helper functions for transforming the output of driver list.
*/
class DirectoryListing {
constructor(driver, listing) {
this.driver = driver;
this.listing = listing;
/**
* Functions chain to be executed for transforming generated listing iterable
*/
this.chain = [];
}
/**
* Filter generated items of listing with the given predicate function.
*/
filter(predicate) {
return this.pipe(async function* (source) {
let index = 0;
for await (const item of source) {
if (await predicate(item, index++, this)) {
yield item;
}
}
});
}
/**
* Transform generated items of listing with the given mapper function.
*/
map(mapper) {
return this.pipe(async function* (source) {
let index = 0;
for await (const item of source) {
yield await mapper(item, index++, this);
}
});
}
/**
* Do recursive listing of items. Without the next function it will do listing of leaf nodes only.
* For advanced usage you can pass the next function which will get as parameter current item and it should
* return the next location for list or null if the recursion should stop and yield the current item.
* For advanced usage you can also limit the depth of recursion using the second argument of next function.
*/
recursive(next = (current) => (current.isFile ? null : current.location)) {
// get copy of current chain until call to recursive
// so we can apply it to listing when doing recursion
const chain = this.chain.slice();
return this.pipe(async function* (source) {
for await (const item of source) {
// call the next function to know if we should continue recursion
const location = await next(item, 0, this);
if (location === null) {
yield item;
}
else {
// create next function to pass to recursive for next level of recursion
// we are using the original function if it is not using the depth parameter
const nextWithDepth = next.length > 1
? (current, depth, driver) => next(current, depth + 1, driver)
: next;
// list the returned location from next function and also apply the chain to it
const list = chain
.reduce((listing, fn) => listing.pipe(fn), this.list(location))
.recursive(nextWithDepth);
const iterator = list[Symbol.asyncIterator]();
let nextItem = await iterator.next();
// if the directory is empty it means it is a leaf and we will yield it
// else we will yield the items in the directory
if (nextItem.done) {
yield item;
}
else {
do {
yield nextItem.value;
nextItem = await iterator.next();
} while (!nextItem.done);
}
}
}
});
}
/**
* Add a piping chain function which gets the current async iterable and returns
* new async iterable with modified directory listing output.
* Function this is bound to instance of driver for which the listing is generated.
* This allows using async generator functions and reference the driver methods easily.
* Piping will always return clone of the current instance and add the function
* to the chain of new cloned instance only to prevent side effects.
*/
pipe(fn) {
const clone = new DirectoryListing(this.driver, this.listing);
clone.chain = this.chain.concat(fn);
return clone;
}
/**
* Get the final async iterable after passing directory listing through chain of piping functions modifying the output.
*/
toIterable() {
return this.chain.reduce((iterable, next) => next.call(this.driver, iterable), this.listing.call(this.driver));
}
/**
* Convert directory listing to array.
*/
async toArray() {
const arr = [];
for await (const item of this.toIterable()) {
arr.push(item);
}
return arr;
}
/**
* A method that returns the default async iterator for an object.
*/
async *[Symbol.asyncIterator]() {
yield* this.toIterable();
}
}
exports.DirectoryListing = DirectoryListing;
+147
View File
@@ -0,0 +1,147 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference path="../../adonis-typings/drive.d.ts" />
/// <reference path="../../test-helpers/drive.d.ts" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
/// <reference types="node" />
/// <reference types="node" />
import { Manager } from '@poppinss/manager';
import { RouterContract } from '@ioc:Adonis/Core/Route';
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { DisksList, Visibility, DriveConfig, DriverContract, DriveFileStats, LocalDriverConfig, DriveManagerContract, FakeImplementationCallback, DirectoryListingContract, DriveListItem } from '@ioc:Adonis/Core/Drive';
import { FakeDrive } from '../Fake';
/**
* Drive manager exposes the API to resolve disks and extend by
* adding custom drivers
*/
export declare class DriveManager extends Manager<ApplicationContract, DriverContract, DriverContract, {
[P in keyof DisksList]: DisksList[P]['implementation'];
}> implements DriveManagerContract {
application: ApplicationContract;
router: RouterContract;
private logger;
private config;
/**
* Find if drive is ready to be used
*/
private isReady;
/**
* The fake callback
*/
private fakeCallback;
/**
* Reference to the fake drive
*/
private fakeDrive;
/**
* Cache all disks instances
*/
protected singleton: boolean;
/**
* Reference to registered fakes
*/
fakes: Map<"local", import("@ioc:Adonis/Core/Drive").FakeDriverContract>;
constructor(application: ApplicationContract, router: RouterContract, logger: LoggerContract, config: DriveConfig);
/**
* Validate config
*/
private validateConfig;
/**
* Returns the default mapping name
*/
protected getDefaultMappingName(): "local";
/**
* Returns config for a given mapping
*/
protected getMappingConfig(diskName: keyof DisksList): LocalDriverConfig;
/**
* Returns the name of the drive used by a given mapping
*/
protected getMappingDriver(diskName: keyof DisksList): string | undefined;
/**
* Make instance of the local driver
*/
protected createLocal(diskName: keyof DisksList, config: LocalDriverConfig): any;
/**
* Fake default or a named disk
*/
fake(disks?: keyof DisksList | keyof DisksList[]): FakeDrive;
/**
* Restore the fake for the default or a named disk
*/
restore(disks?: keyof DisksList | keyof DisksList[]): void;
/**
* Restore all fakes1
*/
restoreAll(): void;
/**
* Resolve instance for a disk
*/
use(disk?: keyof DisksList): any;
/**
* Register a custom fake implementation
*/
setFakeImplementation(callback: FakeImplementationCallback): void;
/**
* Returns the file contents as a buffer. The buffer return
* value allows you to self choose the encoding when
* converting the buffer to a string.
*/
get(location: string, ...args: any[]): Promise<Buffer>;
/**
* Returns the file contents as a stream
*/
getStream(location: string, ...args: any[]): Promise<NodeJS.ReadableStream>;
/**
* A boolean to find if the location path exists or not
*/
exists(location: string, ...args: any[]): Promise<boolean>;
/**
* Returns the location path visibility
*/
getVisibility(location: string, ...args: any[]): Promise<Visibility>;
/**
* Returns the location path stats
*/
getStats(location: string, ...args: any[]): Promise<DriveFileStats>;
/**
* Returns a signed URL for a given location path
*/
getSignedUrl(location: string, ...args: any[]): Promise<string>;
/**
* Returns a URL for a given location path
*/
getUrl(location: string, ...args: any[]): Promise<string>;
/**
* Write string|buffer contents to a destination. The missing
* intermediate directories will be created (if required).
*/
put(location: string, ...args: any[]): Promise<void>;
/**
* Write a stream to a destination. The missing intermediate
* directories will be created (if required).
*/
putStream(location: string, ...args: any[]): Promise<void>;
/**
* Not supported
*/
setVisibility(location: string, ...args: any[]): Promise<void>;
/**
* Remove a given location path
*/
delete(location: string, ...args: any[]): Promise<void>;
/**
* Copy a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
copy(source: string, ...args: any[]): Promise<void>;
/**
* Move a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
move(source: string, ...args: any[]): Promise<void>;
/**
* Return a listing directory iterator for given location.
*/
list(location: string): DirectoryListingContract<DriverContract, DriveListItem>;
}
+236
View File
@@ -0,0 +1,236 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DriveManager = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const manager_1 = require("@poppinss/manager");
const utils_1 = require("@poppinss/utils");
const Fake_1 = require("../Fake");
/**
* Drive manager exposes the API to resolve disks and extend by
* adding custom drivers
*/
class DriveManager extends manager_1.Manager {
constructor(application, router, logger, config) {
super(application);
this.application = application;
this.router = router;
this.logger = logger;
this.config = config;
/**
* Find if drive is ready to be used
*/
this.isReady = false;
/**
* The fake callback
*/
this.fakeCallback = (_, disk, config) => {
const { FakeDriver } = require('../Drivers/Fake');
return new FakeDriver(disk, config, this.router);
};
/**
* Reference to the fake drive
*/
this.fakeDrive = new Fake_1.FakeDrive();
/**
* Cache all disks instances
*/
this.singleton = true;
/**
* Reference to registered fakes
*/
this.fakes = this.fakeDrive.fakes;
this.validateConfig();
}
/**
* Validate config
*/
validateConfig() {
if (!this.config) {
return;
}
const validator = new utils_1.ManagerConfigValidator(this.config, 'drive', 'config/drive');
validator.validateDefault('disk');
validator.validateList('disks', 'disk');
this.isReady = true;
}
/**
* Returns the default mapping name
*/
getDefaultMappingName() {
return this.config.disk;
}
/**
* Returns config for a given mapping
*/
getMappingConfig(diskName) {
return this.config.disks[diskName];
}
/**
* Returns the name of the drive used by a given mapping
*/
getMappingDriver(diskName) {
return this.getMappingConfig(diskName)?.driver;
}
/**
* Make instance of the local driver
*/
createLocal(diskName, config) {
const { LocalDriver } = require('../Drivers/Local');
return new LocalDriver(diskName, config, this.router);
}
/**
* Fake default or a named disk
*/
fake(disks) {
disks = disks || this.getDefaultMappingName();
const disksToFake = Array.isArray(disks) ? disks : [disks];
disksToFake.forEach((disk) => {
if (!this.fakeDrive.isFaked(disk)) {
this.logger.trace({ disk: disk }, 'drive faking disk');
this.fakeDrive.fakes.set(disk, this.fakeCallback(this, disk, this.getMappingConfig(disk)));
}
});
return this.fakeDrive;
}
/**
* Restore the fake for the default or a named disk
*/
restore(disks) {
disks = disks || this.getDefaultMappingName();
const disksToRestore = Array.isArray(disks) ? disks : [disks];
disksToRestore.forEach((disk) => {
if (this.fakeDrive.isFaked(disk)) {
this.logger.trace({ disk: disk }, 'drive restoring disk fake');
this.fakeDrive.restore(disk);
}
});
}
/**
* Restore all fakes1
*/
restoreAll() {
this.fakeDrive.fakes = new Map();
}
/**
* Resolve instance for a disk
*/
use(disk) {
if (!this.isReady) {
throw new utils_1.Exception('Missing configuration for drive. Visit https://bit.ly/2WnR5j9 for setup instructions', 500, 'E_MISSING_DRIVE_CONFIG');
}
disk = disk || this.getDefaultMappingName();
if (this.fakeDrive.isFaked(disk)) {
return this.fakeDrive.use(disk);
}
return super.use(disk);
}
/**
* Register a custom fake implementation
*/
setFakeImplementation(callback) {
this.fakeCallback = callback;
}
/**
* Returns the file contents as a buffer. The buffer return
* value allows you to self choose the encoding when
* converting the buffer to a string.
*/
async get(location, ...args) {
return this.use().get(location, ...args);
}
/**
* Returns the file contents as a stream
*/
async getStream(location, ...args) {
return this.use().getStream(location, ...args);
}
/**
* A boolean to find if the location path exists or not
*/
exists(location, ...args) {
return this.use().exists(location, ...args);
}
/**
* Returns the location path visibility
*/
async getVisibility(location, ...args) {
return this.use().getVisibility(location, ...args);
}
/**
* Returns the location path stats
*/
async getStats(location, ...args) {
return this.use().getStats(location, ...args);
}
/**
* Returns a signed URL for a given location path
*/
getSignedUrl(location, ...args) {
return this.use().getSignedUrl(location, ...args);
}
/**
* Returns a URL for a given location path
*/
getUrl(location, ...args) {
return this.use().getUrl(location, ...args);
}
/**
* Write string|buffer contents to a destination. The missing
* intermediate directories will be created (if required).
*/
put(location, ...args) {
return this.use().put(location, ...args);
}
/**
* Write a stream to a destination. The missing intermediate
* directories will be created (if required).
*/
putStream(location, ...args) {
return this.use().putStream(location, ...args);
}
/**
* Not supported
*/
setVisibility(location, ...args) {
return this.use().setVisibility(location, ...args);
}
/**
* Remove a given location path
*/
delete(location, ...args) {
return this.use().delete(location, ...args);
}
/**
* Copy a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
copy(source, ...args) {
return this.use().copy(source, ...args);
}
/**
* Move a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
move(source, ...args) {
return this.use().move(source, ...args);
}
/**
* Return a listing directory iterator for given location.
*/
list(location) {
const driver = this.use();
if (typeof driver.list !== 'function') {
throw new utils_1.Exception(`List is not supported by the "${driver.name}" driver.`, 500, 'E_LIST_NOT_SUPPORTED');
}
return driver.list(location);
}
}
exports.DriveManager = DriveManager;
+104
View File
@@ -0,0 +1,104 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="node" />
/// <reference types="node" />
import { RouterContract } from '@ioc:Adonis/Core/Route';
import { DisksList, Visibility, ContentHeaders, DriveFileStats, FakeDriverContract, DirectoryListingContract, FakeDriveListItem } from '@ioc:Adonis/Core/Drive';
/**
* Memory driver is mainly used for testing
*/
export declare class FakeDriver implements FakeDriverContract {
disk: keyof DisksList;
private config;
private router;
/**
* Reference to the underlying adapter. Which is memfs
*/
adapter: import("memfs/lib/volume").Volume;
/**
* Name of the driver
*/
name: 'fake';
/**
* Path prefixer used for prefixing paths with disk root
* It doesn't play any role but we will try to construct same path as faked driver is using
* We use the root path if it is available for driver and also add prefix if provided
*/
private prefixer;
/**
* Rely on the config for visibility or fallback to private
*/
private visibility;
constructor(disk: keyof DisksList, config: any, router: RouterContract);
/**
* Make absolute path to a given location
*/
makePath(location: string): string;
/**
* Creates the directory recursively with in the memory
*/
private ensureDir;
/**
* Returns the file contents as a buffer. The buffer return
* value allows you to self choose the encoding when
* converting the buffer to a string.
*/
get(location: string): Promise<Buffer>;
/**
* Returns the file contents as a stream
*/
getStream(location: string): Promise<NodeJS.ReadableStream>;
/**
* A boolean to find if the location path exists or not
*/
exists(location: string): Promise<boolean>;
/**
* Not supported
*/
getVisibility(): Promise<Visibility>;
/**
* Returns the file stats
*/
getStats(location: string): Promise<DriveFileStats>;
/**
* Returns a signed URL for a given location path
*/
getSignedUrl(location: string, options?: ContentHeaders & {
expiresIn?: string | number;
}): Promise<string>;
/**
* Returns a URL for a given location path
*/
getUrl(location: string): Promise<string>;
/**
* Write string|buffer contents to a destination. The missing
* intermediate directories will be created (if required).
*/
put(location: string, contents: Buffer | string): Promise<void>;
/**
* Write a stream to a destination. The missing intermediate
* directories will be created (if required).
*/
putStream(location: string, contents: NodeJS.ReadableStream): Promise<void>;
/**
* Not supported
*/
setVisibility(): Promise<void>;
/**
* Remove a given location path
*/
delete(location: string): Promise<void>;
/**
* Copy a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
copy(source: string, destination: string): Promise<void>;
/**
* Move a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
move(source: string, destination: string): Promise<void>;
/**
* Return a listing directory iterator for given location.
*/
list(location: string): DirectoryListingContract<this, FakeDriveListItem>;
}
+287
View File
@@ -0,0 +1,287 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FakeDriver = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const etag_1 = __importDefault(require("etag"));
const memfs_1 = require("memfs");
const path_1 = require("path");
const utils_1 = require("../utils");
const LocalFileServer_1 = require("../LocalFileServer");
const Exceptions_1 = require("../Exceptions");
const DirectoryListing_1 = require("../DirectoryListing");
const PathPrefixer_1 = require("../PathPrefixer");
/**
* Memory driver is mainly used for testing
*/
class FakeDriver {
constructor(disk, config, router) {
this.disk = disk;
this.config = config;
this.router = router;
/**
* Reference to the underlying adapter. Which is memfs
*/
this.adapter = new memfs_1.Volume();
/**
* Name of the driver
*/
this.name = 'fake';
/**
* Path prefixer used for prefixing paths with disk root
* It doesn't play any role but we will try to construct same path as faked driver is using
* We use the root path if it is available for driver and also add prefix if provided
*/
this.prefixer = PathPrefixer_1.PathPrefixer.fromPath(this.config.root || '/').withPrefix(this.config.prefix || '');
/**
* Rely on the config for visibility or fallback to private
*/
this.visibility = this.config.visibility || 'private';
}
/**
* Make absolute path to a given location
*/
makePath(location) {
return this.prefixer.prefixPath(location);
}
/**
* Creates the directory recursively with in the memory
*/
ensureDir(location) {
return new Promise((resolve, reject) => {
this.adapter.mkdirp((0, path_1.dirname)(location), (error) => {
if (error) {
reject(error);
}
else {
resolve();
}
});
});
}
/**
* Returns the file contents as a buffer. The buffer return
* value allows you to self choose the encoding when
* converting the buffer to a string.
*/
async get(location) {
return new Promise((resolve, reject) => {
this.adapter.readFile(this.makePath(location), (error, data) => {
if (error) {
reject(Exceptions_1.CannotReadFileException.invoke(location, error));
}
else {
resolve(data);
}
});
});
}
/**
* Returns the file contents as a stream
*/
async getStream(location) {
return this.adapter.createReadStream(this.makePath(location));
}
/**
* A boolean to find if the location path exists or not
*/
exists(location) {
return new Promise((resolve) => {
this.adapter.exists(this.makePath(location), (exists) => {
resolve(exists);
});
});
}
/**
* Not supported
*/
async getVisibility() {
return this.visibility;
}
/**
* Returns the file stats
*/
async getStats(location) {
return new Promise((resolve, reject) => {
this.adapter.stat(this.makePath(location), (error, stats) => {
if (error) {
reject(Exceptions_1.CannotGetMetaDataException.invoke(location, 'stats', error));
}
else {
resolve({
modified: stats.mtime,
size: stats.size,
isFile: stats.isFile(),
etag: (0, etag_1.default)(stats),
});
}
});
});
}
/**
* Returns a signed URL for a given location path
*/
async getSignedUrl(location, options) {
const { expiresIn, ...qs } = options || {};
return this.router.makeSignedUrl('/__drive_fake', {
disk: this.disk,
[LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)],
}, {
expiresIn,
qs,
disableRouteLookup: true,
});
}
/**
* Returns a URL for a given location path
*/
async getUrl(location) {
return this.router.makeUrl('/__drive_fake', {
disk: this.disk,
[LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)],
}, {
disableRouteLookup: true,
});
}
/**
* Write string|buffer contents to a destination. The missing
* intermediate directories will be created (if required).
*/
async put(location, contents) {
const absolutePath = this.makePath(location);
await this.ensureDir(absolutePath);
return new Promise((resolve, reject) => {
this.adapter.writeFile(absolutePath, contents, (error) => {
if (error) {
reject(Exceptions_1.CannotWriteFileException.invoke(location, error));
}
else {
resolve();
}
});
});
}
/**
* Write a stream to a destination. The missing intermediate
* directories will be created (if required).
*/
async putStream(location, contents) {
const absolutePath = this.makePath(location);
try {
await this.ensureDir(absolutePath);
const writeStream = this.adapter.createWriteStream(absolutePath);
/**
* If streaming is interrupted, then the destination file will be
* created with partial or empty contents.
*
* Earlier we are cleaning up the empty file, which addresses one
* use case (no pre-existing file was there).
*
* However, in case there was already a file, it will be then emptied
* out. So basically there is no way to get the original contents
* back unless we read the existing content in buffer, but then
* we don't know how large the file is.
*/
await (0, utils_1.pipelinePromise)(contents, writeStream);
}
catch (error) {
throw Exceptions_1.CannotWriteFileException.invoke(location, error);
}
}
/**
* Not supported
*/
async setVisibility() {
return;
}
/**
* Remove a given location path
*/
async delete(location) {
if (!(await this.exists(location))) {
return;
}
return new Promise((resolve, reject) => {
this.adapter.unlink(this.makePath(location), (error) => {
if (error) {
reject(Exceptions_1.CannotDeleteFileException.invoke(location, error));
}
else {
resolve();
}
});
});
}
/**
* Copy a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
async copy(source, destination) {
const desintationAbsolutePath = this.makePath(destination);
await this.ensureDir(desintationAbsolutePath);
return new Promise((resolve, reject) => {
this.adapter.copyFile(this.makePath(source), desintationAbsolutePath, (error) => {
if (error) {
reject(Exceptions_1.CannotCopyFileException.invoke(source, destination, error));
}
else {
resolve();
}
});
});
}
/**
* Move a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
async move(source, destination) {
const sourceAbsolutePath = this.makePath(source);
const desintationAbsolutePath = this.makePath(destination);
await this.ensureDir(desintationAbsolutePath);
return new Promise((resolve, reject) => {
this.adapter.copyFile(sourceAbsolutePath, desintationAbsolutePath, (error) => {
if (error) {
reject(Exceptions_1.CannotMoveFileException.invoke(source, destination, error));
}
else {
resolve();
}
});
}).then(() => this.delete(source));
}
/**
* Return a listing directory iterator for given location.
*/
list(location) {
const fullPath = this.makePath(location);
return new DirectoryListing_1.DirectoryListing(this, async function* () {
try {
const dir = (await this.adapter.promises.readdir(fullPath, {
withFileTypes: true,
}));
const prefixer = this.prefixer.withStrippedPrefix(fullPath);
for (const dirent of dir) {
yield {
location: prefixer.prefixPath(dirent.name),
isFile: dirent.isFile(),
original: dirent,
};
}
}
catch (error) {
throw Exceptions_1.CannotListDirectoryException.invoke(location, error);
}
});
}
}
exports.FakeDriver = FakeDriver;
+97
View File
@@ -0,0 +1,97 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="node" />
/// <reference types="node" />
import * as fsExtra from 'fs-extra';
import { RouterContract } from '@ioc:Adonis/Core/Route';
import { Visibility, DriveFileStats, ContentHeaders, LocalDriverConfig, LocalDriverContract, DirectoryListingContract, LocalDriveListItem } from '@ioc:Adonis/Core/Drive';
/**
* Local driver interacts with the local file system
*/
export declare class LocalDriver implements LocalDriverContract {
private diskName;
private config;
private router;
private routeName;
/**
* Reference to the underlying adapter. Which is
* fs-extra
*/
adapter: typeof fsExtra;
/**
* Name of the driver
*/
name: 'local';
/**
* Path prefixer used for prefixing paths with disk root
*/
private prefixer;
constructor(diskName: string, config: LocalDriverConfig, router: RouterContract);
/**
* Make absolute path to a given location
*/
makePath(location: string): string;
/**
* Returns the file contents as a buffer. The buffer return
* value allows you to self choose the encoding when
* converting the buffer to a string.
*/
get(location: string): Promise<Buffer>;
/**
* Returns the file contents as a stream
*/
getStream(location: string): Promise<NodeJS.ReadableStream>;
/**
* A boolean to find if the location path exists or not
*/
exists(location: string): Promise<boolean>;
/**
* Not supported
*/
getVisibility(_: string): Promise<Visibility>;
/**
* Returns the file stats
*/
getStats(location: string): Promise<DriveFileStats>;
/**
* Returns a signed URL for a given location path
*/
getSignedUrl(location: string, options?: ContentHeaders & {
expiresIn?: string | number;
}): Promise<string>;
/**
* Returns a URL for a given location path
*/
getUrl(location: string): Promise<string>;
/**
* Write string|buffer contents to a destination. The missing
* intermediate directories will be created (if required).
*/
put(location: string, contents: Buffer | string): Promise<void>;
/**
* Write a stream to a destination. The missing intermediate
* directories will be created (if required).
*/
putStream(location: string, contents: NodeJS.ReadableStream): Promise<void>;
/**
* Not supported
*/
setVisibility(_: string, __: string): Promise<void>;
/**
* Remove a given location path
*/
delete(location: string): Promise<void>;
/**
* Copy a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
copy(source: string, destination: string): Promise<void>;
/**
* Move a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
move(source: string, destination: string): Promise<void>;
/**
* Return a listing directory iterator for given location.
*/
list(location: string): DirectoryListingContract<this, LocalDriveListItem>;
}
+266
View File
@@ -0,0 +1,266 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalDriver = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const etag_1 = __importDefault(require("etag"));
const fsExtra = __importStar(require("fs-extra"));
const path_1 = require("path");
const DirectoryListing_1 = require("../DirectoryListing");
const utils_1 = require("../utils");
const LocalFileServer_1 = require("../LocalFileServer");
const PathPrefixer_1 = require("../PathPrefixer");
const Exceptions_1 = require("../Exceptions");
/**
* Local driver interacts with the local file system
*/
class LocalDriver {
constructor(diskName, config, router) {
this.diskName = diskName;
this.config = config;
this.router = router;
this.routeName = LocalFileServer_1.LocalFileServer.makeRouteName(this.diskName);
/**
* Reference to the underlying adapter. Which is
* fs-extra
*/
this.adapter = fsExtra;
/**
* Name of the driver
*/
this.name = 'local';
/**
* Path prefixer used for prefixing paths with disk root
*/
this.prefixer = PathPrefixer_1.PathPrefixer.fromPath(this.config.root);
}
/**
* Make absolute path to a given location
*/
makePath(location) {
return this.prefixer.prefixPath(location);
}
/**
* Returns the file contents as a buffer. The buffer return
* value allows you to self choose the encoding when
* converting the buffer to a string.
*/
async get(location) {
try {
return await this.adapter.readFile(this.makePath(location));
}
catch (error) {
throw Exceptions_1.CannotReadFileException.invoke(location, error);
}
}
/**
* Returns the file contents as a stream
*/
async getStream(location) {
try {
return this.adapter.createReadStream(this.makePath(location));
}
catch (error) {
throw Exceptions_1.CannotReadFileException.invoke(location, error);
}
}
/**
* A boolean to find if the location path exists or not
*/
async exists(location) {
try {
return await this.adapter.pathExists(this.makePath(location));
}
catch (error) {
throw Exceptions_1.CannotGetMetaDataException.invoke(location, 'exists', error);
}
}
/**
* Not supported
*/
async getVisibility(_) {
return this.config.visibility;
}
/**
* Returns the file stats
*/
async getStats(location) {
try {
const stats = await this.adapter.stat(this.makePath(location));
return {
modified: stats.mtime,
size: stats.size,
isFile: stats.isFile(),
etag: (0, etag_1.default)(stats),
};
}
catch (error) {
throw Exceptions_1.CannotGetMetaDataException.invoke(location, 'stats', error);
}
}
/**
* Returns a signed URL for a given location path
*/
async getSignedUrl(location, options) {
if (!this.config.serveFiles) {
throw Exceptions_1.CannotGenerateUrlException.invoke(location, this.diskName);
}
const { expiresIn, ...qs } = options || {};
return this.router.makeSignedUrl(this.routeName, { [LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)] }, {
expiresIn,
qs,
});
}
/**
* Returns a URL for a given location path
*/
async getUrl(location) {
if (!this.config.serveFiles) {
throw Exceptions_1.CannotGenerateUrlException.invoke(location, this.diskName);
}
return this.router.makeUrl(this.routeName, {
[LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)],
});
}
/**
* Write string|buffer contents to a destination. The missing
* intermediate directories will be created (if required).
*/
async put(location, contents) {
try {
await this.adapter.outputFile(this.makePath(location), contents);
}
catch (error) {
throw Exceptions_1.CannotWriteFileException.invoke(location, error);
}
}
/**
* Write a stream to a destination. The missing intermediate
* directories will be created (if required).
*/
async putStream(location, contents) {
const absolutePath = this.makePath(location);
const dir = (0, path_1.dirname)(absolutePath);
await this.adapter.ensureDir(dir);
const writeStream = this.adapter.createWriteStream(absolutePath);
/**
* If streaming is interrupted, then the destination file will be
* created with partial or empty contents.
*
* Earlier we are cleaning up the empty file, which addresses one
* use case (no pre-existing file was there).
*
* However, in case there was already a file, it will be then emptied
* out. So basically there is no way to get the original contents
* back unless we read the existing content in buffer, but then
* we don't know how large the file is.
*/
try {
await (0, utils_1.pipelinePromise)(contents, writeStream);
}
catch (error) {
throw Exceptions_1.CannotWriteFileException.invoke(location, error);
}
}
/**
* Not supported
*/
async setVisibility(_, __) {
return;
}
/**
* Remove a given location path
*/
async delete(location) {
try {
await this.adapter.remove(this.makePath(location));
}
catch (error) {
throw Exceptions_1.CannotDeleteFileException.invoke(location, error);
}
}
/**
* Copy a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
async copy(source, destination) {
try {
await this.adapter.copy(this.makePath(source), this.makePath(destination), {
overwrite: true,
});
}
catch (error) {
throw Exceptions_1.CannotCopyFileException.invoke(source, destination, error);
}
}
/**
* Move a given location path from the source to the desination.
* The missing intermediate directories will be created (if required)
*/
async move(source, destination) {
try {
await this.adapter.move(this.makePath(source), this.makePath(destination), {
overwrite: true,
});
}
catch (error) {
throw Exceptions_1.CannotMoveFileException.invoke(source, destination, error);
}
}
/**
* Return a listing directory iterator for given location.
*/
list(location) {
const fullPath = this.makePath(location);
return new DirectoryListing_1.DirectoryListing(this, async function* () {
try {
const dir = await this.adapter.opendir(fullPath);
const prefixer = this.prefixer.withStrippedPrefix(fullPath);
for await (const dirent of dir) {
yield {
location: prefixer.prefixPath(dirent.name),
isFile: dirent.isFile(),
original: dirent,
};
}
}
catch (error) {
throw Exceptions_1.CannotListDirectoryException.invoke(location, error);
}
});
}
}
exports.LocalDriver = LocalDriver;
+82
View File
@@ -0,0 +1,82 @@
import { Exception } from '@poppinss/utils';
/**
* Unable to write file to the destination
*/
export declare class CannotWriteFileException extends Exception {
location: string;
original: any;
static invoke(location: string, original: any): CannotWriteFileException;
}
/**
* Unable to read file from a given location
*/
export declare class CannotReadFileException extends Exception {
location: string;
original: any;
static invoke(location: string, original: any): CannotReadFileException;
}
/**
* Unable to delete file from a given location
*/
export declare class CannotDeleteFileException extends Exception {
location: string;
original: any;
static invoke(location: string, original: any): CannotDeleteFileException;
}
/**
* Unable to copy file from source to destination
*/
export declare class CannotCopyFileException extends Exception {
source: string;
destination: string;
original: any;
static invoke(source: string, destination: string, original: any): CannotCopyFileException;
}
/**
* Unable to move file from source to destination
*/
export declare class CannotMoveFileException extends Exception {
source: string;
destination: string;
original: any;
static invoke(source: string, destination: string, original: any): CannotMoveFileException;
}
/**
* Unable to get file metadata
*/
export declare class CannotGetMetaDataException extends Exception {
location: string;
operation: string;
original: any;
static invoke(location: string, operation: string, original: any): CannotGetMetaDataException;
}
/**
* Unable to set visibility
*/
export declare class CannotSetVisibilityException extends Exception {
location: string;
original: any;
static invoke(location: string, original: any): CannotSetVisibilityException;
}
/**
* Unable to generate url for a file. The assets serving is disabled
*/
export declare class CannotGenerateUrlException extends Exception {
location: string;
static invoke(location: string, diskName: string): CannotGenerateUrlException;
}
/**
* Unable to list directory contents of given location
*/
export declare class CannotListDirectoryException extends Exception {
location: string;
original: any;
static invoke(location: string, original: any): CannotListDirectoryException;
}
/**
* Given location is trying to traverse beyond the root path
*/
export declare class PathTraversalDetectedException extends Exception {
location: string;
static invoke(location: string): PathTraversalDetectedException;
}
+133
View File
@@ -0,0 +1,133 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathTraversalDetectedException = exports.CannotListDirectoryException = exports.CannotGenerateUrlException = exports.CannotSetVisibilityException = exports.CannotGetMetaDataException = exports.CannotMoveFileException = exports.CannotCopyFileException = exports.CannotDeleteFileException = exports.CannotReadFileException = exports.CannotWriteFileException = void 0;
const utils_1 = require("@poppinss/utils");
/**
* Unable to write file to the destination
*/
class CannotWriteFileException extends utils_1.Exception {
static invoke(location, original) {
const error = new this(`Cannot write file at location "${location}"`, 500, 'E_CANNOT_WRITE_FILE');
error.location = location;
error.original = original;
return error;
}
}
exports.CannotWriteFileException = CannotWriteFileException;
/**
* Unable to read file from a given location
*/
class CannotReadFileException extends utils_1.Exception {
static invoke(location, original) {
const error = new this(`Cannot read file from location "${location}"`, 500, 'E_CANNOT_READ_FILE');
error.location = location;
error.original = original;
return error;
}
}
exports.CannotReadFileException = CannotReadFileException;
/**
* Unable to delete file from a given location
*/
class CannotDeleteFileException extends utils_1.Exception {
static invoke(location, original) {
const error = new this(`Cannot delete file at location "${location}"`, 500, 'E_CANNOT_DELETE_FILE');
error.location = location;
error.original = original;
return error;
}
}
exports.CannotDeleteFileException = CannotDeleteFileException;
/**
* Unable to copy file from source to destination
*/
class CannotCopyFileException extends utils_1.Exception {
static invoke(source, destination, original) {
const error = new this(`Cannot copy file from "${source}" to "${destination}"`, 500, 'E_CANNOT_COPY_FILE');
error.source = source;
error.destination = destination;
error.original = original;
return error;
}
}
exports.CannotCopyFileException = CannotCopyFileException;
/**
* Unable to move file from source to destination
*/
class CannotMoveFileException extends utils_1.Exception {
static invoke(source, destination, original) {
const error = new this(`Cannot move file from "${source}" to "${destination}"`, 500, 'E_CANNOT_MOVE_FILE');
error.source = source;
error.destination = destination;
error.original = original;
return error;
}
}
exports.CannotMoveFileException = CannotMoveFileException;
/**
* Unable to get file metadata
*/
class CannotGetMetaDataException extends utils_1.Exception {
static invoke(location, operation, original) {
const error = new this(`Unable to retrieve the "${operation}" for file at location "${location}"`, 500, 'E_CANNOT_GET_METADATA');
error.location = location;
error.operation = operation;
error.original = original;
return error;
}
}
exports.CannotGetMetaDataException = CannotGetMetaDataException;
/**
* Unable to set visibility
*/
class CannotSetVisibilityException extends utils_1.Exception {
static invoke(location, original) {
const error = new this(`Unable to set visibility for file at location "${location}"`, 500, 'E_CANNOT_SET_VISIBILITY');
error.location = location;
error.original = original;
return error;
}
}
exports.CannotSetVisibilityException = CannotSetVisibilityException;
/**
* Unable to generate url for a file. The assets serving is disabled
*/
class CannotGenerateUrlException extends utils_1.Exception {
static invoke(location, diskName) {
const error = new this(`Cannot generate URL for location "${location}". Make sure to set "serveFiles = true" for "${diskName}" disk`, 500, 'E_CANNOT_GENERATE_URL');
error.location = location;
return error;
}
}
exports.CannotGenerateUrlException = CannotGenerateUrlException;
/**
* Unable to list directory contents of given location
*/
class CannotListDirectoryException extends utils_1.Exception {
static invoke(location, original) {
const error = new this(`Cannot list directory contents of location "${location}"`, 500, 'E_CANNOT_LIST_DIRECTORY');
error.location = location;
error.original = original;
return error;
}
}
exports.CannotListDirectoryException = CannotListDirectoryException;
/**
* Given location is trying to traverse beyond the root path
*/
class PathTraversalDetectedException extends utils_1.Exception {
static invoke(location) {
const error = new this(`Path traversal detected: "${location}"`, 500, 'E_PATH_TRAVERSAL_DETECTED');
error.location = location;
return error;
}
}
exports.PathTraversalDetectedException = PathTraversalDetectedException;
+36
View File
@@ -0,0 +1,36 @@
/// <reference types="node" />
import { DisksList, FakeDriveContract, FakeDriverContract } from '@ioc:Adonis/Core/Drive';
/**
* An implementation of the fake drive
*/
export declare class FakeDrive implements FakeDriveContract {
/**
* Reference to registered fakes
*/
fakes: Map<keyof DisksList, FakeDriverContract>;
/**
* Find a file for the given path exists. Searched
* across all the faked disk
*/
exists(location: string): Promise<boolean>;
/**
* Get the contents of the file as buffer
*/
get(location: string): Promise<Buffer>;
/**
* Get the contents of the file as buffer
*/
bytes(location: string): Promise<number>;
/**
* Access the faked driver
*/
use(disk: keyof DisksList): FakeDriverContract;
/**
* Find if a disk has been faked
*/
isFaked(disk: keyof DisksList): boolean;
/**
* Restore a fake
*/
restore(disk: keyof DisksList): void;
}
+74
View File
@@ -0,0 +1,74 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FakeDrive = void 0;
const Exceptions_1 = require("../Exceptions");
/**
* An implementation of the fake drive
*/
class FakeDrive {
constructor() {
/**
* Reference to registered fakes
*/
this.fakes = new Map();
}
/**
* Find a file for the given path exists. Searched
* across all the faked disk
*/
async exists(location) {
for (let [, fake] of this.fakes) {
const exists = await fake.exists(location);
if (exists) {
return true;
}
}
return false;
}
/**
* Get the contents of the file as buffer
*/
async get(location) {
for (let [, fake] of this.fakes) {
const exists = await fake.exists(location);
if (exists) {
return fake.get(location);
}
}
throw Exceptions_1.CannotReadFileException.invoke(location, new Error('File not found'));
}
/**
* Get the contents of the file as buffer
*/
async bytes(location) {
const contents = await this.get(location);
return contents.length;
}
/**
* Access the faked driver
*/
use(disk) {
return this.fakes.get(disk);
}
/**
* Find if a disk has been faked
*/
isFaked(disk) {
return this.fakes.has(disk);
}
/**
* Restore a fake
*/
restore(disk) {
this.fakes.delete(disk);
}
}
exports.FakeDrive = FakeDrive;
+30
View File
@@ -0,0 +1,30 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { RouterContract } from '@ioc:Adonis/Core/Route';
import { LocalDriverConfig, LocalDriverContract } from '@ioc:Adonis/Core/Drive';
/**
* Registers the route to serve files from the local driver
* or the memory driver.
*/
export declare class LocalFileServer {
private diskName;
private config;
private driver;
private router;
private logger;
/**
* Makes the route name for a given disk name
*/
static makeRouteName(diskName: string): string;
/**
* The file path route param name
*/
static filePathParamName: string;
constructor(diskName: string, config: LocalDriverConfig, driver: LocalDriverContract, router: RouterContract, logger: LoggerContract);
/**
* Registers route for disk using "local" driver and "serveFiles"
* true
*/
registerRoute(): void;
}
+173
View File
@@ -0,0 +1,173 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalFileServer = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const path_1 = require("path");
const utils_1 = require("@poppinss/utils");
/**
* Registers the route to serve files from the local driver
* or the memory driver.
*/
class LocalFileServer {
constructor(diskName, config, driver, router, logger) {
this.diskName = diskName;
this.config = config;
this.driver = driver;
this.router = router;
this.logger = logger;
}
/**
* Makes the route name for a given disk name
*/
static makeRouteName(diskName) {
return `drive.${diskName}.serve`;
}
/**
* Registers route for disk using "local" driver and "serveFiles"
* true
*/
registerRoute() {
/**
* Base path must always be defined
*/
if (!this.config.basePath) {
throw new utils_1.Exception(`Missing property "basePath" in "${this.diskName}" disk config`, 500, 'E_MISSING_LOCAL_DRIVER_BASEPATH');
}
const routeName = LocalFileServer.makeRouteName(this.diskName);
const routePattern = `${this.config.basePath.replace(/\/$/, '')}/${LocalFileServer.filePathParamName}`;
this.logger.trace({ route: routePattern, name: routeName }, 'registering drive route');
this.router
.get(routePattern, async ({ response, request, logger }) => {
let location = request.param(LocalFileServer.filePathParamName).join('/');
try {
location = decodeURIComponent(location);
}
catch (error) {
// keep location should be raised as it is
}
const fileVisibility = await this.driver.getVisibility(location);
const usingSignature = !!request.input('signature');
/**
* Deny request when not using signature and file is "private"
*/
if (!usingSignature && fileVisibility === 'private') {
response.unauthorized('Access denied');
return;
}
/**
* Deny request when using signature but its invalid. File
* visibility doesn't play a role here.
*/
if (usingSignature && !request.hasValidSignature()) {
response.unauthorized('Access denied');
return;
}
/**
* Read https://datatracker.ietf.org/doc/html/rfc7234#section-4.3.5 for
* headers management
*/
try {
const stats = await this.driver.getStats(location);
/**
* Ignore requests for directories
*/
if (!stats.isFile) {
return response.notFound('File not found');
}
/**
* Set Last-Modified or the Cache-Control header. We pick
* the cache control header from the query string only
* when a valid signature is presented.
*/
if (usingSignature && request.input('cacheControl')) {
response.header('Cache-Control', request.input('cacheControl'));
}
else {
response.header('Last-Modified', stats.modified.toUTCString());
}
/**
* Set the Content-Type header. We pick the contentType header
* from the query string only when a valid signature
* is presented
*/
if (usingSignature && request.input('contentType')) {
response.header('Content-Type', request.input('contentType'));
}
else {
response.type((0, path_1.extname)(location));
}
/**
* Set the following headers by reading the query string values. Must
* be done when a signature was presented.
*/
if (usingSignature && request.input('contentDisposition')) {
response.header('Content-Disposition', request.input('contentDisposition'));
}
if (usingSignature && request.input('contentEncoding')) {
response.header('Content-Encoding', request.input('contentEncoding'));
}
if (usingSignature && request.input('contentLanguage')) {
response.header('Content-Language', request.input('contentLanguage'));
}
/**
* Define etag when present in stats
*/
if (stats.etag) {
response.header('etag', stats.etag);
}
/*
* Do not stream files for HEAD request, but set the appropriate
* status code.
*
* 200: When NOT using etags or cache is NOT fresh. This forces browser
* to always make a GET request
*
* 304: When etags are used and cache is fresh
*/
if (request.method() === 'HEAD') {
response.status(response.fresh() ? 304 : 200);
return;
}
/*
* Regardless of request method, if cache is
* fresh, then we must respond with 304
*/
if (response.fresh()) {
response.status(304);
return;
}
/**
* Set content length if serving the file
*/
response.header('Content-length', stats.size.toString());
/**
* Stream file.
*/
return response.stream(await this.driver.getStream(location));
}
catch (error) {
if (error.original?.code === 'ENOENT' || error.code === 'ENOENT') {
response.notFound('File not found');
}
else {
logger.fatal(error, `drive: Unable to serve file "${location}" from "${this.diskName}" disk`);
response.internalServerError('Cannot process file');
}
}
})
.as(routeName);
}
}
exports.LocalFileServer = LocalFileServer;
/**
* The file path route param name
*/
LocalFileServer.filePathParamName = '*';
+46
View File
@@ -0,0 +1,46 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/**
* Path prefixer for resolving and prefixing paths for disk drivers
*/
export declare class PathPrefixer {
/**
* Separator used for dividing path segments is always unix-style forward slash
*/
separator: "/";
/**
* Prefix used for path prefixing. Can be empty string for cloud drivers.
*/
prefix: string;
constructor(prefix?: string);
/**
* Normalize given path to always use `/` as separator and resolve relative paths using `.` and `..`.
* It also guards against path traversal beyond the root.
*/
normalizePath(path: string): string;
/**
* Ruturns normalized and prefixed location path.
*/
prefixPath(location: string): string;
/**
* Ruturns normalized and prefixed location path for directory so always ending with slash.
* Useful for cloud drivers prefix when listitng files.
*/
prefixDirectoryPath(location: string): string;
/**
* Returns normalized path after stripping the current prefix from it.
* It is a reverse operation of `prefixPath`.
*/
stripPrefix(location: string): string;
/**
* Returns a new instance of `PathPrefixer` which is using as prefix stripped prefix from path of current `PathPrefixer`.
*/
withStrippedPrefix(path: string): PathPrefixer;
/**
* Returns a new instance of `PathPrefixer` which is using as prefix current prefix merged with provided prefix.
*/
withPrefix(prefix: string): PathPrefixer;
/**
* Returns a new instance of `PathPrefixer` which is using as prefix provided normalized path.
*/
static fromPath(path: string): PathPrefixer;
}
+96
View File
@@ -0,0 +1,96 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathPrefixer = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const utils_1 = require("@poppinss/utils");
const path_1 = require("path");
const Exceptions_1 = require("../Exceptions");
/**
* Path prefixer for resolving and prefixing paths for disk drivers
*/
class PathPrefixer {
constructor(prefix = '') {
/**
* Separator used for dividing path segments is always unix-style forward slash
*/
this.separator = '/';
// strip slashes from the end of the prefix
this.prefix = prefix.replace(/\/+$/g, '');
// always end prefix with separator if it is not empty
if (this.prefix !== '' || prefix === this.separator) {
this.prefix += this.separator;
}
}
/**
* Normalize given path to always use `/` as separator and resolve relative paths using `.` and `..`.
* It also guards against path traversal beyond the root.
*/
normalizePath(path) {
const converted = (0, utils_1.slash)(path);
const parts = [];
for (const part of converted.split(this.separator)) {
if (['', '.'].includes(part)) {
continue;
}
if (part === '..') {
// if we are traversing beyond the root
if (parts.length === 0) {
throw Exceptions_1.PathTraversalDetectedException.invoke(converted);
}
parts.pop();
}
else {
parts.push(part);
}
}
return parts.join(this.separator);
}
/**
* Ruturns normalized and prefixed location path.
*/
prefixPath(location) {
return this.prefix + this.normalizePath(location);
}
/**
* Ruturns normalized and prefixed location path for directory so always ending with slash.
* Useful for cloud drivers prefix when listitng files.
*/
prefixDirectoryPath(location) {
return this.prefixPath(location) + this.separator;
}
/**
* Returns normalized path after stripping the current prefix from it.
* It is a reverse operation of `prefixPath`.
*/
stripPrefix(location) {
const path = (0, path_1.relative)(this.prefix, (0, utils_1.slash)(location));
return this.normalizePath(path);
}
/**
* Returns a new instance of `PathPrefixer` which is using as prefix stripped prefix from path of current `PathPrefixer`.
*/
withStrippedPrefix(path) {
return new PathPrefixer(this.stripPrefix(path));
}
/**
* Returns a new instance of `PathPrefixer` which is using as prefix current prefix merged with provided prefix.
*/
withPrefix(prefix) {
return new PathPrefixer(this.prefixPath(prefix));
}
/**
* Returns a new instance of `PathPrefixer` which is using as prefix provided normalized path.
*/
static fromPath(path) {
return new this((0, utils_1.slash)((0, path_1.normalize)(path)));
}
}
exports.PathPrefixer = PathPrefixer;
+3
View File
@@ -0,0 +1,3 @@
/// <reference types="node" />
import { pipeline } from 'stream';
export declare const pipelinePromise: typeof pipeline.__promisify__;
+14
View File
@@ -0,0 +1,14 @@
"use strict";
/*
* @adonisjs/drive
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.pipelinePromise = void 0;
const util_1 = require("util");
const stream_1 = require("stream");
exports.pipelinePromise = (0, util_1.promisify)(stream_1.pipeline);