Skip to content

Commit c0dd844

Browse files
committed
implemented HEAD operation
1 parent b4825f4 commit c0dd844

2 files changed

Lines changed: 142 additions & 8 deletions

File tree

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,51 @@
1-
import { WebDavMethodHandler } from '../../types/webdav.types';
21
import { Request, Response } from 'express';
2+
import { WebDavMethodHandler } from '../../types/webdav.types';
3+
import { WebDavUtils } from '../../utils/webdav.utils';
4+
import { webdavLogger } from '../../utils/logger.utils';
5+
import { DriveFileService } from '../../services/drive/drive-file.service';
6+
import { DriveDatabaseManager } from '../../services/database/drive-database-manager.service';
7+
import { DriveFileItem } from '../../types/drive.types';
8+
import { NetworkUtils } from '../../utils/network.utils';
39

410
export class HEADRequestHandler implements WebDavMethodHandler {
5-
handle = async (_: Request, res: Response) => {
6-
// This is a NOOP request handler, clients like CyberDuck uses this.
7-
res.status(405).send();
11+
constructor(
12+
private readonly dependencies: {
13+
driveFileService: DriveFileService;
14+
driveDatabaseManager: DriveDatabaseManager;
15+
},
16+
) {}
17+
18+
handle = async (req: Request, res: Response) => {
19+
const { driveDatabaseManager, driveFileService } = this.dependencies;
20+
const resource = await WebDavUtils.getRequestedResource(req);
21+
22+
if (resource.type === 'folder') {
23+
res.status(200).send();
24+
return;
25+
}
26+
27+
webdavLogger.info(`[HEAD] Request received for ${resource.type} at ${resource.url}`);
28+
const driveFile = (await WebDavUtils.getAndSearchItemFromResource({
29+
resource,
30+
driveDatabaseManager,
31+
driveFileService,
32+
})) as DriveFileItem;
33+
34+
webdavLogger.info(`[HEAD] [${driveFile.uuid}] Found Drive File`);
35+
36+
const range = req.headers['range'];
37+
const rangeOptions = NetworkUtils.parseRangeHeader({
38+
range,
39+
totalFileSize: driveFile.size,
40+
});
41+
let contentLength = driveFile.size;
42+
if (rangeOptions) {
43+
webdavLogger.info(`[HEAD] [${driveFile.uuid}] Range request received:`, { rangeOptions });
44+
contentLength = rangeOptions.rangeSize;
45+
}
46+
47+
res.header('Content-Type', 'application/octet-stream');
48+
res.header('Content-length', contentLength.toString());
49+
res.status(200).send();
850
};
951
}
Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,115 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest';
22
import { HEADRequestHandler } from '../../../src/webdav/handlers/HEAD.handler';
3-
import { createWebDavRequestFixture, createWebDavResponseFixture } from '../../fixtures/webdav.fixture';
3+
import {
4+
createWebDavRequestFixture,
5+
createWebDavResponseFixture,
6+
getRequestedFileResource,
7+
getRequestedFolderResource,
8+
} from '../../fixtures/webdav.fixture';
9+
import { DriveFileService } from '../../../src/services/drive/drive-file.service';
10+
import { getDriveDatabaseManager } from '../../fixtures/drive-database.fixture';
11+
import { WebDavRequestedResource } from '../../../src/types/webdav.types';
12+
import { newFileItem } from '../../fixtures/drive.fixture';
13+
import { WebDavUtils } from '../../../src/utils/webdav.utils';
14+
import { randomInt } from 'crypto';
415

516
describe('HEAD request handler', () => {
617
beforeEach(() => {
718
vi.restoreAllMocks();
819
});
920

10-
it('When a WebDav client sends a HEAD request, it should reply with a 405', async () => {
11-
const requestHandler = new HEADRequestHandler();
21+
it('When a folder is requested, it should reply with a 200', async () => {
22+
const requestHandler = new HEADRequestHandler({
23+
driveDatabaseManager: getDriveDatabaseManager(),
24+
driveFileService: DriveFileService.instance,
25+
});
26+
27+
const requestedFolderResource: WebDavRequestedResource = getRequestedFolderResource();
28+
29+
const request = createWebDavRequestFixture({
30+
method: 'HEAD',
31+
url: requestedFolderResource.url,
32+
headers: {},
33+
});
34+
const response = createWebDavResponseFixture({
35+
status: vi.fn().mockReturnValue({ send: vi.fn() }),
36+
});
37+
38+
await requestHandler.handle(request, response);
39+
expect(response.status).toHaveBeenCalledWith(200);
40+
});
41+
42+
it('When a file is requested, it should reply with a 200 with the correct headers', async () => {
43+
const requestHandler = new HEADRequestHandler({
44+
driveDatabaseManager: getDriveDatabaseManager(),
45+
driveFileService: DriveFileService.instance,
46+
});
47+
48+
const requestedFileResource: WebDavRequestedResource = getRequestedFileResource();
49+
50+
const request = createWebDavRequestFixture({
51+
method: 'HEAD',
52+
url: requestedFileResource.url,
53+
headers: {},
54+
});
55+
const response = createWebDavResponseFixture({
56+
status: vi.fn().mockReturnValue({ send: vi.fn() }),
57+
header: vi.fn(),
58+
});
59+
60+
const mockFile = newFileItem();
61+
62+
const getRequestedResourceStub = vi
63+
.spyOn(WebDavUtils, 'getRequestedResource')
64+
.mockResolvedValue(requestedFileResource);
65+
const getAndSearchItemFromResourceStub = vi
66+
.spyOn(WebDavUtils, 'getAndSearchItemFromResource')
67+
.mockResolvedValue(mockFile);
68+
69+
await requestHandler.handle(request, response);
70+
expect(response.status).toHaveBeenCalledWith(200);
71+
expect(response.header).toHaveBeenCalledWith('Content-Type', 'application/octet-stream');
72+
expect(response.header).toHaveBeenCalledWith('Content-length', mockFile.size.toString());
73+
expect(getRequestedResourceStub).toHaveBeenCalledOnce();
74+
expect(getAndSearchItemFromResourceStub).toHaveBeenCalledOnce();
75+
});
76+
77+
it('When a file is requested with range-request, it should reply with a 200 with the correct headers', async () => {
78+
const requestHandler = new HEADRequestHandler({
79+
driveDatabaseManager: getDriveDatabaseManager(),
80+
driveFileService: DriveFileService.instance,
81+
});
82+
83+
const requestedFileResource: WebDavRequestedResource = getRequestedFileResource();
84+
85+
const mockSize = randomInt(500, 10000);
86+
const mockFile = newFileItem({ size: mockSize });
87+
const rangeStart = randomInt(0, 450);
1288

1389
const request = createWebDavRequestFixture({
1490
method: 'HEAD',
91+
url: requestedFileResource.url,
92+
headers: {
93+
range: `bytes=${rangeStart}-${mockSize}`,
94+
},
1595
});
1696
const response = createWebDavResponseFixture({
1797
status: vi.fn().mockReturnValue({ send: vi.fn() }),
98+
header: vi.fn(),
1899
});
19100

101+
const getRequestedResourceStub = vi
102+
.spyOn(WebDavUtils, 'getRequestedResource')
103+
.mockResolvedValue(requestedFileResource);
104+
const getAndSearchItemFromResourceStub = vi
105+
.spyOn(WebDavUtils, 'getAndSearchItemFromResource')
106+
.mockResolvedValue(mockFile);
107+
20108
await requestHandler.handle(request, response);
21-
expect(response.status).toHaveBeenCalledWith(405);
109+
expect(response.status).toHaveBeenCalledWith(200);
110+
expect(response.header).toHaveBeenCalledWith('Content-length', (mockSize - rangeStart).toString());
111+
expect(response.header).toHaveBeenCalledWith('Content-Type', 'application/octet-stream');
112+
expect(getRequestedResourceStub).toHaveBeenCalledOnce();
113+
expect(getAndSearchItemFromResourceStub).toHaveBeenCalledOnce();
22114
});
23115
});

0 commit comments

Comments
 (0)