Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions WEBDAV.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Below you can find a list of WebDav clients that we officially support in the In
| CyberDuck for MacOS | ✅ |
| Transmit | ✅ |
| Cadaver | ✅ |
| Nautilus (GNOME Files)| ✅ |

## Supported WebDav methods

Expand Down
1 change: 1 addition & 0 deletions src/services/database/drive-file/drive-file.domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class DriveFile implements DriveFileAttributes {

public toItem(): DriveFileItem {
return {
itemType: 'file',
id: this.id,
name: this.name,
type: this.type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class DriveFolder implements DriveFolderAttributes {

public toItem(): DriveFolderItem {
return {
itemType: 'folder',
id: this.id,
name: this.name,
uuid: this.uuid,
Expand Down
1 change: 1 addition & 0 deletions src/services/drive/drive-file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class DriveFileService {
const driveFile = await storageClient.createFileEntryByUuid(payload);

return {
itemType: 'file',
name: payload.plainName,
id: driveFile.id,
uuid: driveFile.uuid,
Expand Down
2 changes: 2 additions & 0 deletions src/types/drive.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type DriveFileItem = Omit<
| 'modificationTime'
| 'type'
> & {
itemType: 'file';
size: number;
createdAt: Date;
updatedAt: Date;
Expand All @@ -21,6 +22,7 @@ export type DriveFileItem = Omit<
};

export type DriveFolderItem = Pick<DriveFolderData, 'name' | 'bucket' | 'id' | 'parentId'> & {
itemType: 'folder';
encryptedName: string;
uuid: string;
createdAt: Date;
Expand Down
1 change: 0 additions & 1 deletion src/types/webdav.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export type WebDavMethodHandlerOptions = {
};

export type WebDavRequestedResource = {
type: 'file' | 'folder';
url: string;
name: string;
path: ParsedPath;
Expand Down
3 changes: 3 additions & 0 deletions src/utils/drive.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DriveFileItem, DriveFolderItem } from '../types/drive.types';
export class DriveUtils {
static driveFileMetaToItem(fileMeta: FileMeta): DriveFileItem {
return {
itemType: 'file',
uuid: fileMeta.uuid ?? '',
status: fileMeta.status,
folderId: fileMeta.folderId,
Expand All @@ -23,6 +24,7 @@ export class DriveUtils {

static driveFolderMetaToItem(folderMeta: FolderMeta): DriveFolderItem {
return {
itemType: 'folder',
uuid: folderMeta.uuid,
id: folderMeta.id,
bucket: folderMeta.bucket,
Expand All @@ -38,6 +40,7 @@ export class DriveUtils {

static createFolderResponseToItem(folderResponse: CreateFolderResponse): DriveFolderItem {
return {
itemType: 'folder',
uuid: folderResponse.uuid,
id: folderResponse.id,
bucket: folderResponse.bucket,
Expand Down
105 changes: 58 additions & 47 deletions src/utils/webdav.utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Request } from 'express';
import path from 'node:path';
import { WebDavRequestedResource } from '../types/webdav.types';
import { DriveFolderService } from '../services/drive/drive-folder.service';
import { DriveFileService } from '../services/drive/drive-file.service';
import { DriveFileItem, DriveFolderItem, DriveItem } from '../types/drive.types';
import { webdavLogger } from './logger.utils';

export class WebDavUtils {
static joinURL(...pathComponents: string[]): string {
Expand Down Expand Up @@ -39,74 +39,85 @@ export class WebDavUtils {
return normalizedPath;
}

static async getRequestedResource(urlObject: string | Request, decodeUri = true): Promise<WebDavRequestedResource> {
let requestUrl: string;
if (typeof urlObject === 'string') {
requestUrl = urlObject;
} else {
requestUrl = urlObject.url;
}

static async getRequestedResource(requestUrl: string, decodeUri = true): Promise<WebDavRequestedResource> {
const decodedUrl = this.decodeUrl(requestUrl, decodeUri);
const parsedPath = path.parse(decodedUrl);
const parentPath = this.normalizeFolderPath(path.dirname(decodedUrl));

const isFolder = requestUrl.endsWith('/');

if (isFolder) {
return {
type: 'folder',
url: decodedUrl,
name: parsedPath.base,
path: parsedPath,
parentPath,
};
} else {
return {
type: 'file',
url: decodedUrl,
name: parsedPath.name,
path: parsedPath,
parentPath,
};
}
return {
url: decodedUrl,
name: parsedPath.base,
path: parsedPath,
parentPath,
};
}

static async getDriveItemFromResource(params: {
resource: WebDavRequestedResource;
static async tryGetFileOrFolderMetadata({
url,
driveFileService,
driveFolderService,
}: {
url: string;
driveFolderService: DriveFolderService;
driveFileService?: never;
}): Promise<DriveFolderItem | undefined>;
driveFileService: DriveFileService;
}): Promise<DriveItem | undefined> {
try {
return await driveFileService.getFileMetadataByPath(url);
} catch {
return await driveFolderService.getFolderMetadataByPath(url);
}
}

static async getDriveItemFromResource(params: {
resource: WebDavRequestedResource;
driveFolderService?: never;
static async getDriveFileFromResource({
url,
driveFileService,
}: {
url: string;
driveFileService: DriveFileService;
}): Promise<DriveFileItem | undefined>;
}): Promise<DriveFileItem | undefined> {
try {
return await driveFileService.getFileMetadataByPath(url);
} catch (err) {
webdavLogger.error('Exception while getting the file metadata by path', err);
}
}

static async getDriveItemFromResource(params: {
resource: WebDavRequestedResource;
static async getDriveFolderFromResource({
url,
driveFolderService,
}: {
url: string;
driveFolderService: DriveFolderService;
driveFileService: DriveFileService;
}): Promise<DriveItem | undefined>;
}): Promise<DriveFolderItem | undefined> {
try {
return await driveFolderService.getFolderMetadataByPath(url);
} catch (err) {
webdavLogger.error('Exception while getting the folder metadata by path', err);
}
}

static async getDriveItemFromResource({
resource,
driveFolderService,
driveFileService,
}: {
resource: WebDavRequestedResource;
driveFolderService?: DriveFolderService;
driveFileService?: DriveFileService;
driveFolderService: DriveFolderService;
driveFileService: DriveFileService;
}): Promise<DriveItem | undefined> {
let item: DriveItem | undefined = undefined;

const isFolder = resource.url.endsWith('/');

try {
if (resource.type === 'folder') {
item = await driveFolderService?.getFolderMetadataByPath(resource.url);
}
if (resource.type === 'file') {
item = await driveFileService?.getFileMetadataByPath(resource.url);
if (isFolder) {
item = await driveFolderService.getFolderMetadataByPath(resource.url);
} else {
item = await this.tryGetFileOrFolderMetadata({
url: resource.url,
driveFileService,
driveFolderService,
});
}
} catch {
//no op
Expand Down
10 changes: 5 additions & 5 deletions src/webdav/handlers/DELETE.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export class DELETERequestHandler implements WebDavMethodHandler {

handle = async (req: Request, res: Response) => {
const { driveFileService, driveFolderService, trashService } = this.dependencies;
const resource = await WebDavUtils.getRequestedResource(req);
webdavLogger.info(`[DELETE] Request received for ${resource.type} at ${resource.url}`);
const resource = await WebDavUtils.getRequestedResource(req.url);
webdavLogger.info(`[DELETE] Request received for item at ${resource.url}`);

const driveItem = await WebDavUtils.getDriveItemFromResource({
resource,
Expand All @@ -31,13 +31,13 @@ export class DELETERequestHandler implements WebDavMethodHandler {
throw new NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
}

webdavLogger.info(`[DELETE] [${driveItem.uuid}] Trashing ${resource.type}`);
webdavLogger.info(`[DELETE] [${driveItem.uuid}] Trashing ${driveItem.itemType}`);
await trashService.trashItems({
items: [{ type: resource.type, uuid: driveItem.uuid, id: null }],
items: [{ type: driveItem.itemType, uuid: driveItem.uuid, id: null }],
});

res.status(204).send();
const type = resource.type.charAt(0).toUpperCase() + resource.type.substring(1);
const type = driveItem.itemType.charAt(0).toUpperCase() + driveItem.itemType.substring(1);
webdavLogger.info(`[DELETE] [${driveItem.uuid}] ${type} trashed successfully`);
};
}
17 changes: 8 additions & 9 deletions src/webdav/handlers/GET.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { CryptoService } from '../../services/crypto.service';
import { AuthService } from '../../services/auth.service';
import { NotFoundError } from '../../utils/errors.utils';
import { webdavLogger } from '../../utils/logger.utils';
import { DriveFileItem } from '../../types/drive.types';
import { NetworkUtils } from '../../utils/network.utils';

export class GETRequestHandler implements WebDavMethodHandler {
Expand All @@ -24,21 +23,21 @@ export class GETRequestHandler implements WebDavMethodHandler {

handle = async (req: Request, res: Response) => {
const { driveFileService, authService, networkFacade } = this.dependencies;
const resource = await WebDavUtils.getRequestedResource(req);
const resource = await WebDavUtils.getRequestedResource(req.url);

if (resource.name.startsWith('._')) throw new NotFoundError('File not found');
if (resource.type === 'folder') throw new NotFoundError('Folders cannot be listed with GET. Use PROPFIND instead.');

webdavLogger.info(`[GET] Request received for ${resource.type} at ${resource.url}`);
const driveItem = await WebDavUtils.getDriveItemFromResource({
resource,
webdavLogger.info(`[GET] Request received item at ${resource.url}`);
const driveFile = await WebDavUtils.getDriveFileFromResource({
url: resource.url,
driveFileService,
});

if (!driveItem) {
throw new NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
if (!driveFile) {
throw new NotFoundError(
`Resource not found on Internxt Drive at ${resource.url}, if trying to access a folder use PROPFIND instead.`,
);
}
const driveFile = driveItem as DriveFileItem;

webdavLogger.info(`[GET] [${driveFile.uuid}] Found Drive File`);

Expand Down
17 changes: 5 additions & 12 deletions src/webdav/handlers/HEAD.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { WebDavMethodHandler } from '../../types/webdav.types';
import { WebDavUtils } from '../../utils/webdav.utils';
import { webdavLogger } from '../../utils/logger.utils';
import { DriveFileService } from '../../services/drive/drive-file.service';
import { DriveFileItem } from '../../types/drive.types';
import { NetworkUtils } from '../../utils/network.utils';
import { NotFoundError } from '../../utils/errors.utils';

Expand All @@ -16,25 +15,19 @@ export class HEADRequestHandler implements WebDavMethodHandler {

handle = async (req: Request, res: Response) => {
const { driveFileService } = this.dependencies;
const resource = await WebDavUtils.getRequestedResource(req);
const resource = await WebDavUtils.getRequestedResource(req.url);

if (resource.type === 'folder') {
res.status(200).send();
return;
}

webdavLogger.info(`[HEAD] Request received for ${resource.type} at ${resource.url}`);
webdavLogger.info(`[HEAD] Request received for file at ${resource.url}`);

try {
const driveItem = await WebDavUtils.getDriveItemFromResource({
resource,
const driveFile = await WebDavUtils.getDriveFileFromResource({
url: resource.url,
driveFileService,
});

if (!driveItem) {
if (!driveFile) {
throw new NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
}
const driveFile = driveItem as DriveFileItem;

webdavLogger.info(`[HEAD] [${driveFile.uuid}] Found Drive File`);

Expand Down
8 changes: 4 additions & 4 deletions src/webdav/handlers/MKCOL.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ export class MKCOLRequestHandler implements WebDavMethodHandler {

handle = async (req: Request, res: Response) => {
const { driveFolderService, webDavFolderService } = this.dependencies;
const resource = await WebDavUtils.getRequestedResource(req);
const resource = await WebDavUtils.getRequestedResource(req.url);

webdavLogger.info(`[MKCOL] Request received for ${resource.type} at ${resource.url}`);
webdavLogger.info(`[MKCOL] Request received for folder at ${resource.url}`);

const parentDriveFolderItem =
(await webDavFolderService.getDriveFolderItemFromPath(resource.parentPath)) ??
(await webDavFolderService.createParentPathOrThrow(resource.parentPath));

const driveFolderItem = await WebDavUtils.getDriveItemFromResource({
resource,
const driveFolderItem = await WebDavUtils.getDriveFolderFromResource({
url: resource.url,
driveFolderService,
});

Expand Down
Loading
Loading