Skip to content
19 changes: 17 additions & 2 deletions src/utils/webdav.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DriveDatabaseManager } from '../services/database/drive-database-manage
import { DriveFolderService } from '../services/drive/drive-folder.service';
import { DriveFileService } from '../services/drive/drive-file.service';
import { DriveFileItem, DriveFolderItem } from '../types/drive.types';
import { NotFoundError } from './errors.utils';
import { ConflictError, NotFoundError } from './errors.utils';
import { webdavLogger } from './logger.utils';

export class WebDavUtils {
Expand All @@ -34,6 +34,7 @@ export class WebDavUtils {
} else {
requestUrl = urlObject.url;
}

const decodedUrl = decodeURIComponent(requestUrl).replaceAll('/./', '/');
const parsedPath = path.parse(decodedUrl);
let parentPath = path.dirname(decodedUrl);
Expand Down Expand Up @@ -95,8 +96,22 @@ export class WebDavUtils {
driveFileService?: DriveFileService,
): Promise<DriveFileItem | DriveFolderItem | undefined> {
let item: DriveFileItem | DriveFolderItem | undefined = undefined;

if (resource.type === 'folder') {
item = await driveFolderService?.getFolderMetadataByPath(resource.url);
// if resource has a parentPath it means it's a subfolder then try to get it; if it throws an error it means it doesn't
// exist and we should throw a 409 error in compliance with the WebDAV RFC
// catch the error during getting parent folder and throw a 409 error in compliance with the WebDAV RFC
try {
item = await driveFolderService?.getFolderMetadataByPath(resource.url);
}
catch (error: any) {
// if the error is a 404 error, it means the resource doesn't exist
// in this case, throw a 409 error in compliance with the WebDAV RFC
if (error.status === 404) {
throw new ConflictError(`Resource not found on Internxt Drive at ${resource.url}`);
}
throw error;
}
}
if (resource.type === 'file') {
item = await driveFileService?.getFileMetadataByPath(resource.url);
Expand Down
22 changes: 21 additions & 1 deletion src/webdav/handlers/MKCOL.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { webdavLogger } from '../../utils/logger.utils';
import { XMLUtils } from '../../utils/xml.utils';
import { AsyncUtils } from '../../utils/async.utils';
import { DriveFolderItem } from '../../types/drive.types';
import { MethodNotAllowed } from '../../utils/errors.utils';

export class MKCOLRequestHandler implements WebDavMethodHandler {
constructor(
private readonly dependencies: {
driveDatabaseManager: DriveDatabaseManager;
driveFolderService: DriveFolderService;
},
) {}
) { }

handle = async (req: Request, res: Response) => {
const { driveDatabaseManager, driveFolderService } = this.dependencies;
Expand All @@ -30,6 +31,25 @@ export class MKCOLRequestHandler implements WebDavMethodHandler {
driveFolderService,
})) as DriveFolderItem;

var folderAlreadyExists = true;
// try to get the folder from the drive before creating it
// The method getFolderMetadataByPath will throw an error if the folder does not exist, so we need to catch it
try {
await driveFolderService.getFolderMetadataByPath(resource.url);
}
// In this case a 404 error must spefically catched
catch (error: any) {
// check if the error is a 404 error
if (error.status === 404) {
folderAlreadyExists = false;
}
}

if (folderAlreadyExists) {
webdavLogger.info(`[MKCOL] ❌ Folder already exists`);
throw new MethodNotAllowed('Folder already exists');
}

const [createFolder] = driveFolderService.createFolder({
plainName: resource.path.base,
parentFolderUuid: parentFolderItem.uuid,
Expand Down
2 changes: 1 addition & 1 deletion src/webdav/handlers/PUT.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,6 @@ export class PUTRequestHandler implements WebDavMethodHandler {

await driveDatabaseManager.createFile(file, resource.path.dir + '/');

res.status(200).send();
res.status(201).send();
};
}
24 changes: 24 additions & 0 deletions src/webdav/middewares/mkcol.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RequestHandler } from 'express';

export const MkcolMiddleware: RequestHandler = (req, _, next) => {
// if request content types are not 'application/xml', 'text/xml' or not defined, return 415
if (req.method === 'MKCOL') {
if (req.get('Content-Type')) {
if (!req.is('application/xml') && !req.is('text/xml')) {
return next({
status: 415,
message: 'Unsupported Media Type',
});
}
}
// body must be empty
if (req.body && Object.keys(req.body).length > 0) {
return next({
status: 415,
message: 'Unsupported Media Type',
});
}
}

next();
};
2 changes: 2 additions & 0 deletions src/webdav/webdav-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { MOVERequestHandler } from './handlers/MOVE.handler';
import { COPYRequestHandler } from './handlers/COPY.handler';
import { TrashService } from '../services/drive/trash.service';
import { Environment } from '@internxt/inxt-js';
import { MkcolMiddleware } from './middewares/mkcol.middleware';

export class WebDavServer {
constructor(
Expand Down Expand Up @@ -70,6 +71,7 @@ export class WebDavServer {
private readonly registerMiddlewares = async () => {
this.app.use(bodyParser.text({ type: ['application/xml', 'text/xml'] }));
this.app.use(ErrorHandlingMiddleware);
this.app.use(MkcolMiddleware);
this.app.use(AuthMiddleware(AuthService.instance));
this.app.use(
RequestLoggerMiddleware({
Expand Down
4 changes: 2 additions & 2 deletions test/webdav/handlers/PUT.handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('PUT request handler', () => {
const createDBFileStub = vi.spyOn(driveDatabaseManager, 'createFile').mockResolvedValue(fileFixture);

await sut.handle(request, response);
expect(response.status).toHaveBeenCalledWith(200);
expect(response.status).toHaveBeenCalledWith(201);
expect(getRequestedResourceStub).toHaveBeenCalledTimes(2);
expect(getAndSearchItemFromResourceStub).toHaveBeenCalledTimes(2);
expect(getAuthDetailsStub).toHaveBeenCalledOnce();
Expand Down Expand Up @@ -206,7 +206,7 @@ describe('PUT request handler', () => {
const createDBFileStub = vi.spyOn(driveDatabaseManager, 'createFile').mockResolvedValue(fileFixture);

await sut.handle(request, response);
expect(response.status).toHaveBeenCalledWith(200);
expect(response.status).toHaveBeenCalledWith(201);
expect(getRequestedResourceStub).toHaveBeenCalledTimes(2);
expect(getAndSearchItemFromResourceStub).toHaveBeenCalledTimes(2);
expect(getAuthDetailsStub).toHaveBeenCalledOnce();
Expand Down
Loading