@internxt/cli 1.6.6 → 1.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -231
- package/dist/commands/create-folder.js +2 -6
- package/dist/commands/delete-permanently-file.js +1 -1
- package/dist/commands/delete-permanently-folder.js +1 -1
- package/dist/commands/download-file.js +3 -3
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +6 -4
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +1 -11
- package/dist/commands/move-file.d.ts +1 -0
- package/dist/commands/move-file.js +1 -1
- package/dist/commands/move-folder.js +1 -1
- package/dist/commands/rename-file.js +1 -1
- package/dist/commands/rename-folder.js +1 -1
- package/dist/commands/trash-file.js +1 -1
- package/dist/commands/trash-folder.js +1 -1
- package/dist/commands/trash-list.d.ts +2 -0
- package/dist/commands/trash-list.js +5 -3
- package/dist/commands/trash-restore-file.d.ts +1 -0
- package/dist/commands/trash-restore-file.js +1 -1
- package/dist/commands/trash-restore-folder.js +1 -1
- package/dist/commands/upload-file.d.ts +1 -0
- package/dist/commands/upload-file.js +22 -13
- package/dist/commands/whoami.js +6 -3
- package/dist/hooks/prerun/auth_check.js +2 -2
- package/dist/services/auth.service.d.ts +3 -3
- package/dist/services/auth.service.js +35 -44
- package/dist/services/crypto.service.d.ts +0 -4
- package/dist/services/crypto.service.js +1 -32
- package/dist/services/database/drive-item/drive-item.model.js +1 -0
- package/dist/services/database/drive-item/drive-item.repository.js +12 -2
- package/dist/services/drive/drive-file.service.d.ts +2 -0
- package/dist/services/drive/drive-file.service.js +48 -18
- package/dist/services/drive/drive-folder.service.d.ts +2 -0
- package/dist/services/drive/drive-folder.service.js +14 -46
- package/dist/services/drive/trash.service.d.ts +2 -0
- package/dist/services/drive/trash.service.js +14 -41
- package/dist/services/network/upload/upload-file.service.js +5 -9
- package/dist/services/network/upload/upload.types.d.ts +2 -1
- package/dist/services/sdk-manager.service.d.ts +0 -2
- package/dist/services/sdk-manager.service.js +0 -7
- package/dist/services/thumbnail.service.d.ts +4 -13
- package/dist/services/thumbnail.service.js +7 -21
- package/dist/services/universal-link.service.d.ts +1 -1
- package/dist/services/universal-link.service.js +2 -2
- package/dist/services/validation.service.d.ts +3 -13
- package/dist/services/validation.service.js +4 -28
- package/dist/types/command.types.d.ts +1 -29
- package/dist/types/command.types.js +1 -15
- package/dist/types/network.types.d.ts +3 -11
- package/dist/types/webdav.types.d.ts +0 -3
- package/dist/utils/cli.utils.js +9 -22
- package/dist/utils/drive.utils.d.ts +1 -0
- package/dist/utils/drive.utils.js +5 -1
- package/dist/utils/format.utils.js +6 -2
- package/dist/utils/logger.utils.js +9 -2
- package/dist/utils/network.utils.js +2 -2
- package/dist/utils/stream.utils.d.ts +2 -2
- package/dist/utils/stream.utils.js +7 -6
- package/dist/utils/thumbnail.utils.d.ts +1 -3
- package/dist/utils/thumbnail.utils.js +3 -13
- package/dist/utils/upload.utils.d.ts +1 -1
- package/dist/utils/upload.utils.js +2 -2
- package/dist/utils/webdav.utils.d.ts +2 -0
- package/dist/utils/webdav.utils.js +16 -0
- package/dist/webdav/handlers/GET.handler.js +4 -2
- package/dist/webdav/handlers/HEAD.handler.js +2 -0
- package/dist/webdav/handlers/MKCOL.handler.js +3 -3
- package/dist/webdav/handlers/PROPFIND.handler.js +8 -4
- package/dist/webdav/handlers/PUT.handler.js +24 -10
- package/dist/webdav/index.js +2 -2
- package/dist/webdav/middewares/auth.middleware.js +2 -1
- package/dist/webdav/middewares/errors.middleware.js +29 -5
- package/dist/webdav/middewares/webdav-auth.middleware.js +1 -0
- package/dist/webdav/webdav-server.js +2 -2
- package/oclif.manifest.json +9 -198
- package/package.json +23 -22
- package/dist/commands/workspaces-list.d.ts +0 -20
- package/dist/commands/workspaces-list.js +0 -68
- package/dist/commands/workspaces-unset.d.ts +0 -23
- package/dist/commands/workspaces-unset.js +0 -46
- package/dist/commands/workspaces-use.d.ts +0 -27
- package/dist/commands/workspaces-use.js +0 -114
- package/dist/services/drive/workspace.service.d.ts +0 -7
- package/dist/services/drive/workspace.service.js +0 -30
package/dist/utils/cli.utils.js
CHANGED
|
@@ -118,7 +118,7 @@ class CLIUtils {
|
|
|
118
118
|
options: { type: 'input' },
|
|
119
119
|
},
|
|
120
120
|
}, {
|
|
121
|
-
validate: validation_service_1.ValidationService.instance.
|
|
121
|
+
validate: validation_service_1.ValidationService.instance.validateUUID,
|
|
122
122
|
error: new command_types_1.NotValidFolderUuidError(),
|
|
123
123
|
canBeEmpty: true,
|
|
124
124
|
}, reporter);
|
|
@@ -255,27 +255,14 @@ class CLIUtils {
|
|
|
255
255
|
}
|
|
256
256
|
};
|
|
257
257
|
static getNetworkCreds = async (userCredentials) => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
mnemonic: currentWorkspace.workspaceData.workspaceUser.key,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
return {
|
|
271
|
-
bucket: userCredentials.bucket,
|
|
272
|
-
credentials: {
|
|
273
|
-
user: userCredentials.bridgeUser,
|
|
274
|
-
pass: userCredentials.userId,
|
|
275
|
-
},
|
|
276
|
-
mnemonic: userCredentials.mnemonic,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
258
|
+
return {
|
|
259
|
+
bucket: userCredentials.bucket,
|
|
260
|
+
credentials: {
|
|
261
|
+
user: userCredentials.bridgeUser,
|
|
262
|
+
pass: userCredentials.userId,
|
|
263
|
+
},
|
|
264
|
+
mnemonic: userCredentials.mnemonic,
|
|
265
|
+
};
|
|
279
266
|
};
|
|
280
267
|
}
|
|
281
268
|
exports.CLIUtils = CLIUtils;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FileMeta, FolderMeta, CreateFolderResponse } from '@internxt/sdk/dist/drive/storage/types';
|
|
2
2
|
import { DriveFileItem, DriveFolderItem } from '../types/drive.types';
|
|
3
3
|
export declare class DriveUtils {
|
|
4
|
+
static parseFileSize(size: string | number): number;
|
|
4
5
|
static driveFileMetaToItem(fileMeta: FileMeta): DriveFileItem;
|
|
5
6
|
static driveFolderMetaToItem(folderMeta: FolderMeta): DriveFolderItem;
|
|
6
7
|
static createFolderResponseToItem(folderResponse: CreateFolderResponse): DriveFolderItem;
|
|
@@ -3,13 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DriveUtils = void 0;
|
|
4
4
|
const types_1 = require("@internxt/sdk/dist/drive/storage/types");
|
|
5
5
|
class DriveUtils {
|
|
6
|
+
static parseFileSize(size) {
|
|
7
|
+
const parsed = Number(size);
|
|
8
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
9
|
+
}
|
|
6
10
|
static driveFileMetaToItem(fileMeta) {
|
|
7
11
|
return {
|
|
8
12
|
itemType: 'file',
|
|
9
13
|
uuid: fileMeta.uuid ?? '',
|
|
10
14
|
status: fileMeta.status,
|
|
11
15
|
folderUuid: fileMeta.folderUuid,
|
|
12
|
-
size:
|
|
16
|
+
size: DriveUtils.parseFileSize(fileMeta.size),
|
|
13
17
|
name: fileMeta.plainName ?? fileMeta.name,
|
|
14
18
|
bucket: fileMeta.bucket,
|
|
15
19
|
createdAt: new Date(fileMeta.createdAt),
|
|
@@ -15,10 +15,14 @@ class FormatUtils {
|
|
|
15
15
|
return Number((size / Math.pow(1024, unitIndex)).toFixed(2)) + ' ' + units[unitIndex];
|
|
16
16
|
};
|
|
17
17
|
static formatDate = (date) => {
|
|
18
|
-
|
|
18
|
+
const d = (0, dayjs_1.default)(date);
|
|
19
|
+
return d.isValid() ? d.format('D MMMM, YYYY [at] HH:mm') : '-';
|
|
19
20
|
};
|
|
20
21
|
static formatDateForWebDav = (date) => {
|
|
21
|
-
|
|
22
|
+
const d = dayjs_1.default.utc(date);
|
|
23
|
+
return d.isValid()
|
|
24
|
+
? d.format('ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
|
25
|
+
: dayjs_1.default.utc().format('ddd, DD MMM YYYY HH:mm:ss [GMT]');
|
|
22
26
|
};
|
|
23
27
|
static formatLimit = (limit) => {
|
|
24
28
|
let result = '...';
|
|
@@ -4,14 +4,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.webdavLogger = exports.logger = void 0;
|
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
7
8
|
const winston_1 = __importDefault(require("winston"));
|
|
8
9
|
const configs_1 = require("../constants/configs");
|
|
10
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
9
11
|
const maxLogSize = 40 * 1024 * 1024;
|
|
10
12
|
const maxLogsFiles = 5;
|
|
13
|
+
const environmentMeta = {
|
|
14
|
+
version: package_json_1.default.version,
|
|
15
|
+
os: `${node_os_1.default.platform()} ${node_os_1.default.release()} (${node_os_1.default.arch()})`,
|
|
16
|
+
node: process.version,
|
|
17
|
+
};
|
|
11
18
|
exports.logger = winston_1.default.createLogger({
|
|
12
19
|
level: 'info',
|
|
13
20
|
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
|
|
14
|
-
defaultMeta: { service: 'internxt-cli' },
|
|
21
|
+
defaultMeta: { service: 'internxt-cli', ...environmentMeta },
|
|
15
22
|
transports: [
|
|
16
23
|
new winston_1.default.transports.File({
|
|
17
24
|
filename: 'internxt-cli-error.log',
|
|
@@ -33,7 +40,7 @@ exports.logger = winston_1.default.createLogger({
|
|
|
33
40
|
exports.webdavLogger = winston_1.default.createLogger({
|
|
34
41
|
level: 'info',
|
|
35
42
|
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
|
|
36
|
-
defaultMeta: { service: 'internxt-webdav' },
|
|
43
|
+
defaultMeta: { service: 'internxt-webdav', ...environmentMeta },
|
|
37
44
|
transports: [
|
|
38
45
|
new winston_1.default.transports.File({
|
|
39
46
|
filename: 'internxt-webdav-error.log',
|
|
@@ -100,10 +100,10 @@ class NetworkUtils {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
else if (parsed === -1) {
|
|
103
|
-
throw new Error(`
|
|
103
|
+
throw new Error(`Unsatisfiable Range-Request. ${JSON.stringify(rangeOptions)}`);
|
|
104
104
|
}
|
|
105
105
|
else if (parsed === -2) {
|
|
106
|
-
throw new Error(`
|
|
106
|
+
throw new Error(`Malformed Range-Request. ${JSON.stringify(rangeOptions)}`);
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
109
|
throw new Error(`Unknown error from Range-Request. ${JSON.stringify(rangeOptions)}`);
|
|
@@ -6,10 +6,10 @@ export declare class StreamUtils {
|
|
|
6
6
|
static joinReadableBinaryStreams(streams: ReadableStream<Uint8Array>[]): ReadableStream<Uint8Array>;
|
|
7
7
|
}
|
|
8
8
|
export declare class BufferStream extends Transform {
|
|
9
|
-
|
|
9
|
+
private chunks;
|
|
10
10
|
constructor(opts?: TransformOptions);
|
|
11
11
|
_transform(chunk: Buffer, _: BufferEncoding, callback: TransformCallback): void;
|
|
12
12
|
_flush(callback: TransformCallback): void;
|
|
13
13
|
reset(): void;
|
|
14
|
-
getBuffer(): Buffer |
|
|
14
|
+
getBuffer(): Buffer | undefined;
|
|
15
15
|
}
|
|
@@ -56,24 +56,25 @@ class StreamUtils {
|
|
|
56
56
|
}
|
|
57
57
|
exports.StreamUtils = StreamUtils;
|
|
58
58
|
class BufferStream extends node_stream_1.Transform {
|
|
59
|
-
|
|
59
|
+
chunks;
|
|
60
60
|
constructor(opts) {
|
|
61
61
|
super(opts);
|
|
62
|
-
this.
|
|
62
|
+
this.chunks = [];
|
|
63
63
|
}
|
|
64
64
|
_transform(chunk, _, callback) {
|
|
65
|
-
|
|
66
|
-
this.buffer = Buffer.concat([currentBuffer, chunk]);
|
|
65
|
+
this.chunks.push(chunk);
|
|
67
66
|
callback(null, chunk);
|
|
68
67
|
}
|
|
69
68
|
_flush(callback) {
|
|
70
69
|
callback();
|
|
71
70
|
}
|
|
72
71
|
reset() {
|
|
73
|
-
this.
|
|
72
|
+
this.chunks = [];
|
|
74
73
|
}
|
|
75
74
|
getBuffer() {
|
|
76
|
-
|
|
75
|
+
if (this.chunks.length === 0)
|
|
76
|
+
return undefined;
|
|
77
|
+
return Buffer.concat(this.chunks);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
exports.BufferStream = BufferStream;
|
|
@@ -5,8 +5,6 @@ export declare const ThumbnailConfig: {
|
|
|
5
5
|
readonly Type: "png";
|
|
6
6
|
};
|
|
7
7
|
export declare class ThumbnailUtils {
|
|
8
|
-
static readonly
|
|
9
|
-
static readonly isFileThumbnailable: (fileType: string) => boolean;
|
|
10
|
-
static readonly isPDFThumbnailable: (fileType: string) => boolean;
|
|
8
|
+
static readonly MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES: number;
|
|
11
9
|
static readonly isImageThumbnailable: (fileType: string, size: number) => boolean;
|
|
12
10
|
}
|
|
@@ -18,9 +18,6 @@ const imageExtensions = {
|
|
|
18
18
|
raw: ['raw', 'cr2', 'nef', 'orf', 'sr2'],
|
|
19
19
|
webp: ['webp'],
|
|
20
20
|
};
|
|
21
|
-
const pdfExtensions = {
|
|
22
|
-
pdf: ['pdf'],
|
|
23
|
-
};
|
|
24
21
|
const thumbnailableImageExtension = new Set([
|
|
25
22
|
...imageExtensions['jpg'],
|
|
26
23
|
...imageExtensions['png'],
|
|
@@ -28,19 +25,12 @@ const thumbnailableImageExtension = new Set([
|
|
|
28
25
|
...imageExtensions['gif'],
|
|
29
26
|
...imageExtensions['tiff'],
|
|
30
27
|
]);
|
|
31
|
-
const thumbnailablePdfExtension = new Set(pdfExtensions['pdf']);
|
|
32
|
-
const thumbnailableExtension = new Set(thumbnailableImageExtension);
|
|
33
28
|
class ThumbnailUtils {
|
|
34
|
-
static
|
|
35
|
-
static isFileThumbnailable = (fileType) => {
|
|
36
|
-
return fileType.trim().length > 0 && thumbnailableExtension.has(fileType.trim().toLowerCase());
|
|
37
|
-
};
|
|
38
|
-
static isPDFThumbnailable = (fileType) => {
|
|
39
|
-
return fileType.trim().length > 0 && thumbnailablePdfExtension.has(fileType.trim().toLowerCase());
|
|
40
|
-
};
|
|
29
|
+
static MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES = 128 * 1024 * 1024;
|
|
41
30
|
static isImageThumbnailable = (fileType, size) => {
|
|
42
|
-
if (size > ThumbnailUtils.
|
|
31
|
+
if (size <= 0 || size > ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES) {
|
|
43
32
|
return false;
|
|
33
|
+
}
|
|
44
34
|
return fileType.trim().length > 0 && thumbnailableImageExtension.has(fileType.trim().toLowerCase());
|
|
45
35
|
};
|
|
46
36
|
}
|
|
@@ -2,7 +2,7 @@ import { Readable } from 'node:stream';
|
|
|
2
2
|
import { BufferStream } from './stream.utils';
|
|
3
3
|
export declare class UploadUtils {
|
|
4
4
|
static readonly checkUploadSizeLimits: (size: number) => Promise<void>;
|
|
5
|
-
static readonly prepareUploadStreams: (readable: Readable, fileType: string) => {
|
|
5
|
+
static readonly prepareUploadStreams: (readable: Readable, fileType: string, size: number) => {
|
|
6
6
|
fileStream: Readable;
|
|
7
7
|
thumbnailStream: BufferStream | undefined;
|
|
8
8
|
isThumbnailable: boolean;
|
|
@@ -19,8 +19,8 @@ class UploadUtils {
|
|
|
19
19
|
throw new Error('File is too big (more than 100 GB)');
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
-
static prepareUploadStreams = (readable, fileType) => {
|
|
23
|
-
const isThumbnailable = thumbnail_utils_1.ThumbnailUtils.
|
|
22
|
+
static prepareUploadStreams = (readable, fileType, size) => {
|
|
23
|
+
const isThumbnailable = thumbnail_utils_1.ThumbnailUtils.isImageThumbnailable(fileType, size);
|
|
24
24
|
if (!isThumbnailable) {
|
|
25
25
|
return { fileStream: readable, thumbnailStream: undefined, isThumbnailable };
|
|
26
26
|
}
|
|
@@ -13,4 +13,6 @@ export declare class WebDavUtils {
|
|
|
13
13
|
itemType: 'file' | 'folder';
|
|
14
14
|
uuid: string;
|
|
15
15
|
}>(driveItem: T): Promise<void>;
|
|
16
|
+
static generateETag(parts: Array<string | number | null | undefined>): string;
|
|
17
|
+
static getItemETag(driveItem: DriveFileItem | DriveFolderItem): string;
|
|
16
18
|
}
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.WebDavUtils = void 0;
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
9
|
const drive_item_service_1 = require("../services/drive/drive-item.service");
|
|
9
10
|
const logger_utils_1 = require("./logger.utils");
|
|
10
11
|
const config_service_1 = require("../services/config.service");
|
|
@@ -100,5 +101,20 @@ class WebDavUtils {
|
|
|
100
101
|
}
|
|
101
102
|
await drive_item_repository_1.DriveItemRepository.instance.delete([driveItem.uuid]);
|
|
102
103
|
}
|
|
104
|
+
static generateETag(parts) {
|
|
105
|
+
const normalized = parts.map((part) => part ?? '-');
|
|
106
|
+
const hash = (0, node_crypto_1.createHash)('sha256').update(normalized.join('|')).digest('hex');
|
|
107
|
+
return `"${hash}"`;
|
|
108
|
+
}
|
|
109
|
+
static getItemETag(driveItem) {
|
|
110
|
+
return this.generateETag([
|
|
111
|
+
driveItem.uuid,
|
|
112
|
+
driveItem.itemType === 'file' ? driveItem.size : undefined,
|
|
113
|
+
driveItem.createdAt.getTime(),
|
|
114
|
+
driveItem.updatedAt.getTime(),
|
|
115
|
+
driveItem.creationTime.getTime(),
|
|
116
|
+
driveItem.modificationTime.getTime(),
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
103
119
|
}
|
|
104
120
|
exports.WebDavUtils = WebDavUtils;
|
|
@@ -20,6 +20,8 @@ class GETRequestHandler {
|
|
|
20
20
|
const { user } = await auth_service_1.AuthService.instance.getAuthDetails();
|
|
21
21
|
logger_utils_1.webdavLogger.info(`[GET] [${driveFile.uuid}] Network ready for download`);
|
|
22
22
|
res.header('Content-Type', 'application/octet-stream');
|
|
23
|
+
res.header('Accept-Ranges', 'bytes');
|
|
24
|
+
res.header('ETag', webdav_utils_1.WebDavUtils.getItemETag(driveFile));
|
|
23
25
|
const fileSize = driveFile.size ?? 0;
|
|
24
26
|
if (fileSize > 0) {
|
|
25
27
|
const range = req.headers['range'];
|
|
@@ -44,8 +46,8 @@ class GETRequestHandler {
|
|
|
44
46
|
if (!driveFile.fileId) {
|
|
45
47
|
throw new command_types_1.NotValidFileIdError();
|
|
46
48
|
}
|
|
47
|
-
const { networkFacade,
|
|
48
|
-
const [executeDownload] = await networkFacade.downloadToStream(bucket, mnemonic, driveFile.fileId, contentLength, writable, rangeOptions);
|
|
49
|
+
const { networkFacade, mnemonic } = await cli_utils_1.CLIUtils.prepareNetwork(user);
|
|
50
|
+
const [executeDownload] = await networkFacade.downloadToStream(driveFile.bucket, mnemonic, driveFile.fileId, contentLength, writable, rangeOptions);
|
|
49
51
|
logger_utils_1.webdavLogger.info(`[GET] [${driveFile.uuid}] Download prepared, executing...`);
|
|
50
52
|
res.status(200);
|
|
51
53
|
await executeDownload;
|
|
@@ -14,6 +14,7 @@ class HEADRequestHandler {
|
|
|
14
14
|
throw new errors_utils_1.NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
|
|
15
15
|
}
|
|
16
16
|
logger_utils_1.webdavLogger.info(`[HEAD] [${driveItem.uuid}] Found Drive item`);
|
|
17
|
+
res.header('ETag', webdav_utils_1.WebDavUtils.getItemETag(driveItem));
|
|
17
18
|
if (driveItem.itemType === 'file') {
|
|
18
19
|
const range = req.headers['range'];
|
|
19
20
|
const rangeOptions = network_utils_1.NetworkUtils.parseRangeHeader({
|
|
@@ -26,6 +27,7 @@ class HEADRequestHandler {
|
|
|
26
27
|
contentLength = rangeOptions.rangeSize;
|
|
27
28
|
}
|
|
28
29
|
res.header('Content-Type', 'application/octet-stream');
|
|
30
|
+
res.header('Accept-Ranges', 'bytes');
|
|
29
31
|
res.header('Content-length', contentLength.toString());
|
|
30
32
|
}
|
|
31
33
|
res.status(200).send();
|
|
@@ -7,6 +7,7 @@ const logger_utils_1 = require("../../utils/logger.utils");
|
|
|
7
7
|
const xml_utils_1 = require("../../utils/xml.utils");
|
|
8
8
|
const webdav_folder_service_1 = require("../../services/webdav/webdav-folder.service");
|
|
9
9
|
const async_utils_1 = require("../../utils/async.utils");
|
|
10
|
+
const errors_utils_1 = require("../../utils/errors.utils");
|
|
10
11
|
class MKCOLRequestHandler {
|
|
11
12
|
handle = async (req, res) => {
|
|
12
13
|
const resource = await webdav_utils_1.WebDavUtils.getRequestedResource(req.url);
|
|
@@ -16,9 +17,8 @@ class MKCOLRequestHandler {
|
|
|
16
17
|
const driveFolderItem = await webdav_utils_1.WebDavUtils.getDriveFolderFromResource(resource.url);
|
|
17
18
|
const folderAlreadyExists = !!driveFolderItem;
|
|
18
19
|
if (folderAlreadyExists) {
|
|
19
|
-
logger_utils_1.webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists,
|
|
20
|
-
|
|
21
|
-
return;
|
|
20
|
+
logger_utils_1.webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists, throwing 405 error`);
|
|
21
|
+
throw new errors_utils_1.MethodNotAllowed('Folder already exists');
|
|
22
22
|
}
|
|
23
23
|
const newFolder = await webdav_folder_service_1.WebDavFolderService.instance.createFolder({
|
|
24
24
|
folderName: resource.path.base,
|
|
@@ -8,8 +8,8 @@ const xml_utils_1 = require("../../utils/xml.utils");
|
|
|
8
8
|
const drive_item_domain_1 = require("../../services/database/drive-item/drive-item.domain");
|
|
9
9
|
const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
|
|
10
10
|
const drive_folder_service_1 = require("../../services/drive/drive-folder.service");
|
|
11
|
+
const drive_utils_1 = require("../../utils/drive.utils");
|
|
11
12
|
const format_utils_1 = require("../../utils/format.utils");
|
|
12
|
-
const node_crypto_1 = require("node:crypto");
|
|
13
13
|
const mime_types_1 = __importDefault(require("mime-types"));
|
|
14
14
|
const webdav_utils_1 = require("../../utils/webdav.utils");
|
|
15
15
|
const logger_utils_1 = require("../../utils/logger.utils");
|
|
@@ -23,6 +23,7 @@ class PROPFINDRequestHandler {
|
|
|
23
23
|
res.status(404).send();
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
+
res.set('Content-Type', 'application/xml; charset="utf-8"');
|
|
26
27
|
switch (driveItem.itemType) {
|
|
27
28
|
case 'file': {
|
|
28
29
|
const fileMetaXML = await this.getFileMetaXML(resource, driveItem);
|
|
@@ -113,7 +114,7 @@ class PROPFINDRequestHandler {
|
|
|
113
114
|
type: file.type,
|
|
114
115
|
status: file.status,
|
|
115
116
|
folderUuid: file.folderUuid,
|
|
116
|
-
size:
|
|
117
|
+
size: drive_utils_1.DriveUtils.parseFileSize(file.size),
|
|
117
118
|
creationTime: new Date(file.creationTime),
|
|
118
119
|
modificationTime: new Date(file.modificationTime),
|
|
119
120
|
createdAt: new Date(file.createdAt),
|
|
@@ -140,7 +141,8 @@ class PROPFINDRequestHandler {
|
|
|
140
141
|
[xml_utils_1.XMLUtils.addDefaultNamespace('propstat')]: {
|
|
141
142
|
[xml_utils_1.XMLUtils.addDefaultNamespace('status')]: 'HTTP/1.1 200 OK',
|
|
142
143
|
[xml_utils_1.XMLUtils.addDefaultNamespace('prop')]: {
|
|
143
|
-
[xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: '
|
|
144
|
+
[xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: 'httpd/unix-directory',
|
|
145
|
+
[xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: webdav_utils_1.WebDavUtils.getItemETag(driveFolderItem),
|
|
144
146
|
'x1:lastmodified': {
|
|
145
147
|
'#text': format_utils_1.FormatUtils.formatDateForWebDav(driveFolderItem.updatedAt),
|
|
146
148
|
'@_xmlns:x1': 'SAR:',
|
|
@@ -173,6 +175,8 @@ class PROPFINDRequestHandler {
|
|
|
173
175
|
[xml_utils_1.XMLUtils.addDefaultNamespace('displayname')]: displayName,
|
|
174
176
|
[xml_utils_1.XMLUtils.addDefaultNamespace('getlastmodified')]: format_utils_1.FormatUtils.formatDateForWebDav(driveFolderItem.updatedAt),
|
|
175
177
|
[xml_utils_1.XMLUtils.addDefaultNamespace('getcontentlength')]: 0,
|
|
178
|
+
[xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: 'httpd/unix-directory',
|
|
179
|
+
[xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: webdav_utils_1.WebDavUtils.getItemETag(driveFolderItem),
|
|
176
180
|
[xml_utils_1.XMLUtils.addDefaultNamespace('resourcetype')]: {
|
|
177
181
|
[xml_utils_1.XMLUtils.addDefaultNamespace('collection')]: '',
|
|
178
182
|
},
|
|
@@ -190,7 +194,7 @@ class PROPFINDRequestHandler {
|
|
|
190
194
|
[xml_utils_1.XMLUtils.addDefaultNamespace('status')]: 'HTTP/1.1 200 OK',
|
|
191
195
|
[xml_utils_1.XMLUtils.addDefaultNamespace('prop')]: {
|
|
192
196
|
[xml_utils_1.XMLUtils.addDefaultNamespace('resourcetype')]: '',
|
|
193
|
-
[xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]:
|
|
197
|
+
[xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: webdav_utils_1.WebDavUtils.getItemETag(driveFileItem),
|
|
194
198
|
[xml_utils_1.XMLUtils.addDefaultNamespace('displayname')]: displayName,
|
|
195
199
|
[xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: mime_types_1.default.lookup(displayName) || 'application/octet-stream',
|
|
196
200
|
[xml_utils_1.XMLUtils.addDefaultNamespace('getlastmodified')]: lastModified,
|
|
@@ -39,16 +39,10 @@ class PUTRequestHandler {
|
|
|
39
39
|
}
|
|
40
40
|
isReplacement = true;
|
|
41
41
|
logger_utils_1.webdavLogger.info(`[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`);
|
|
42
|
-
try {
|
|
43
|
-
await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
|
|
44
|
-
await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
}
|
|
48
42
|
}
|
|
49
43
|
const { user } = await auth_service_1.AuthService.instance.getAuthDetails();
|
|
50
44
|
const fileType = resource.path.ext.replace('.', '');
|
|
51
|
-
const { fileStream, thumbnailStream } = upload_utils_1.UploadUtils.prepareUploadStreams(req, fileType);
|
|
45
|
+
const { fileStream, thumbnailStream } = upload_utils_1.UploadUtils.prepareUploadStreams(req, fileType, contentLength);
|
|
52
46
|
const { networkFacade, bucket } = await cli_utils_1.CLIUtils.prepareNetwork(user);
|
|
53
47
|
let fileId;
|
|
54
48
|
if (contentLength > 0) {
|
|
@@ -79,7 +73,7 @@ class PUTRequestHandler {
|
|
|
79
73
|
logger_utils_1.webdavLogger.info('[PUT] ✅ File uploaded to network');
|
|
80
74
|
}
|
|
81
75
|
const driveTimer = cli_utils_1.CLIUtils.timer();
|
|
82
|
-
const
|
|
76
|
+
const filePayload = {
|
|
83
77
|
plainName: resource.path.name,
|
|
84
78
|
type: fileType,
|
|
85
79
|
size: contentLength,
|
|
@@ -87,7 +81,26 @@ class PUTRequestHandler {
|
|
|
87
81
|
fileId,
|
|
88
82
|
bucket,
|
|
89
83
|
encryptVersion: types_1.EncryptionVersion.Aes03,
|
|
90
|
-
}
|
|
84
|
+
};
|
|
85
|
+
let file;
|
|
86
|
+
if (driveFileItem?.itemType === 'file' && contentLength > 0) {
|
|
87
|
+
try {
|
|
88
|
+
file = await drive_file_service_1.DriveFileService.instance.replaceFile(driveFileItem.uuid, filePayload);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
logger_utils_1.webdavLogger.warn(`[PUT] File replace failed for '${resource.url}', falling back to delete and create: ${error instanceof Error ? error.message : String(error)}`);
|
|
92
|
+
await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
|
|
93
|
+
await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
|
|
94
|
+
file = await drive_file_service_1.DriveFileService.instance.createFile(filePayload);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (driveFileItem?.itemType === 'file') {
|
|
99
|
+
await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
|
|
100
|
+
await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
|
|
101
|
+
}
|
|
102
|
+
file = await drive_file_service_1.DriveFileService.instance.createFile(filePayload);
|
|
103
|
+
}
|
|
91
104
|
timings.driveUpload = driveTimer.stop();
|
|
92
105
|
await drive_item_repository_1.DriveItemRepository.instance.createOrUpdate([
|
|
93
106
|
{
|
|
@@ -101,7 +114,7 @@ class PUTRequestHandler {
|
|
|
101
114
|
const thumbnailTimer = cli_utils_1.CLIUtils.timer();
|
|
102
115
|
await thumbnail_service_1.ThumbnailService.instance.tryUploadThumbnail({
|
|
103
116
|
fileUuid: file.uuid,
|
|
104
|
-
|
|
117
|
+
input: thumbnailStream?.getBuffer(),
|
|
105
118
|
fileType,
|
|
106
119
|
bucket,
|
|
107
120
|
networkFacade,
|
|
@@ -115,6 +128,7 @@ class PUTRequestHandler {
|
|
|
115
128
|
const statusCode = isReplacement ? 204 : 201;
|
|
116
129
|
logger_utils_1.webdavLogger.info(`[PUT] [RESPONSE-${statusCode}] ${resource.url} - Returning ${statusCode} ` +
|
|
117
130
|
`after ${cli_utils_1.CLIUtils.formatDuration(totalTime)}`);
|
|
131
|
+
res.header('ETag', webdav_utils_1.WebDavUtils.getItemETag(file));
|
|
118
132
|
res.status(statusCode).send();
|
|
119
133
|
};
|
|
120
134
|
}
|
package/dist/webdav/index.js
CHANGED
|
@@ -18,8 +18,8 @@ const init = async () => {
|
|
|
18
18
|
await config_service_1.ConfigService.instance.ensureInternxtLogsDirExists();
|
|
19
19
|
await database_service_1.DatabaseService.instance.initialize();
|
|
20
20
|
await database_service_1.DatabaseService.instance.clear();
|
|
21
|
-
const { token
|
|
22
|
-
sdk_manager_service_1.SdkManager.init({ token
|
|
21
|
+
const { token } = await auth_service_1.AuthService.instance.getAuthDetails();
|
|
22
|
+
sdk_manager_service_1.SdkManager.init({ token });
|
|
23
23
|
new webdav_server_1.WebDavServer((0, express_1.default)())
|
|
24
24
|
.start()
|
|
25
25
|
.then()
|
|
@@ -13,7 +13,7 @@ const AuthMiddleware = () => {
|
|
|
13
13
|
try {
|
|
14
14
|
const cached = cache_service_1.CacheService.instance.get(cache_service_1.CacheService.AUTH_CACHE_KEY);
|
|
15
15
|
if (cached) {
|
|
16
|
-
sdk_manager_service_1.SdkManager.init({ token: cached.token
|
|
16
|
+
sdk_manager_service_1.SdkManager.init({ token: cached.token });
|
|
17
17
|
next();
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
@@ -35,6 +35,7 @@ const AuthMiddleware = () => {
|
|
|
35
35
|
const errorBodyXML = xml_utils_1.XMLUtils.toWebDavXML({
|
|
36
36
|
[xml_utils_1.XMLUtils.addDefaultNamespace('responsedescription')]: message,
|
|
37
37
|
}, {}, 'error');
|
|
38
|
+
res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
|
|
38
39
|
res.status(401).send(errorBodyXML);
|
|
39
40
|
}
|
|
40
41
|
})();
|
|
@@ -4,8 +4,35 @@ exports.ErrorHandlingMiddleware = void 0;
|
|
|
4
4
|
const logger_utils_1 = require("../../utils/logger.utils");
|
|
5
5
|
const xml_utils_1 = require("../../utils/xml.utils");
|
|
6
6
|
const errors_utils_1 = require("../../utils/errors.utils");
|
|
7
|
+
const getErrorDetail = (err) => {
|
|
8
|
+
if (typeof err !== 'object' || err === null || !('data' in err))
|
|
9
|
+
return undefined;
|
|
10
|
+
const data = err.data;
|
|
11
|
+
if (typeof data !== 'object' || data === null || !('message' in data))
|
|
12
|
+
return undefined;
|
|
13
|
+
const message = data.message;
|
|
14
|
+
if (typeof message === 'string' && message.trim().length > 0)
|
|
15
|
+
return message;
|
|
16
|
+
if (Array.isArray(message) && message.length > 0)
|
|
17
|
+
return message.join(', ');
|
|
18
|
+
return undefined;
|
|
19
|
+
};
|
|
20
|
+
const getErrorStatusCode = (err) => {
|
|
21
|
+
if (typeof err !== 'object' || err === null)
|
|
22
|
+
return undefined;
|
|
23
|
+
const { statusCode, status } = err;
|
|
24
|
+
if (typeof statusCode === 'number' && !Number.isNaN(statusCode))
|
|
25
|
+
return statusCode;
|
|
26
|
+
if (typeof status === 'number' && !Number.isNaN(status))
|
|
27
|
+
return status;
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
7
30
|
const ErrorHandlingMiddleware = (err, req, res, _) => {
|
|
8
|
-
|
|
31
|
+
let message = errors_utils_1.ErrorUtils.isError(err) ? err.message : 'Something went wrong';
|
|
32
|
+
const detail = getErrorDetail(err);
|
|
33
|
+
if (detail) {
|
|
34
|
+
message += ` [${detail}]`;
|
|
35
|
+
}
|
|
9
36
|
if (errors_utils_1.ErrorUtils.isError(err) && err.stack) {
|
|
10
37
|
logger_utils_1.webdavLogger.error(`[ERROR MIDDLEWARE] [${req.method.toUpperCase()} - ${req.url}] ${message}\nStack: ${err.stack}`);
|
|
11
38
|
}
|
|
@@ -15,10 +42,7 @@ const ErrorHandlingMiddleware = (err, req, res, _) => {
|
|
|
15
42
|
const errorBodyXML = xml_utils_1.XMLUtils.toWebDavXML({
|
|
16
43
|
[xml_utils_1.XMLUtils.addDefaultNamespace('responsedescription')]: message,
|
|
17
44
|
}, {}, 'error');
|
|
18
|
-
|
|
19
|
-
if ('statusCode' in err && !Number.isNaN(err.statusCode)) {
|
|
20
|
-
statusCode = err.statusCode;
|
|
21
|
-
}
|
|
45
|
+
const statusCode = getErrorStatusCode(err) ?? 500;
|
|
22
46
|
res.set('Content-Type', 'application/xml; charset="utf-8"');
|
|
23
47
|
res.status(statusCode).send(errorBodyXML);
|
|
24
48
|
req.destroy();
|
|
@@ -37,5 +37,6 @@ const sendUnauthorizedError = (res, message) => {
|
|
|
37
37
|
const errorBodyXML = xml_utils_1.XMLUtils.toWebDavXML({
|
|
38
38
|
[xml_utils_1.XMLUtils.addDefaultNamespace('responsedescription')]: message,
|
|
39
39
|
}, {}, 'error');
|
|
40
|
+
res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
|
|
40
41
|
res.status(401).send(errorBodyXML);
|
|
41
42
|
};
|
|
@@ -35,11 +35,11 @@ class WebDavServer {
|
|
|
35
35
|
this.app = app;
|
|
36
36
|
}
|
|
37
37
|
registerStartMiddlewares = (configs) => {
|
|
38
|
-
this.app.use((0, webdav_auth_middleware_1.WebDAVAuthMiddleware)(configs));
|
|
39
|
-
this.app.use((0, auth_middleware_1.AuthMiddleware)());
|
|
40
38
|
this.app.use((0, request_logger_middleware_1.RequestLoggerMiddleware)({
|
|
41
39
|
enable: true,
|
|
42
40
|
}));
|
|
41
|
+
this.app.use((0, webdav_auth_middleware_1.WebDAVAuthMiddleware)(configs));
|
|
42
|
+
this.app.use((0, auth_middleware_1.AuthMiddleware)());
|
|
43
43
|
this.app.use(body_parser_1.default.text({ type: ['application/xml', 'text/xml'] }));
|
|
44
44
|
this.app.use(mkcol_middleware_1.MkcolMiddleware);
|
|
45
45
|
};
|