@nextcloud/files 3.5.1 → 3.6.0
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 +18 -1
- package/dist/files/node.d.ts +11 -0
- package/dist/files/nodeData.d.ts +2 -0
- package/dist/index.cjs +106 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.mjs +107 -10
- package/dist/index.mjs.map +1 -1
- package/dist/utils/filename-validation.d.ts +51 -0
- package/dist/utils/filename.d.ts +0 -6
- package/package.json +11 -10
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,8 @@ export { FileType } from './files/fileType';
|
|
|
12
12
|
export { File, type IFile } from './files/file';
|
|
13
13
|
export { Folder, type IFolder } from './files/folder';
|
|
14
14
|
export { Node, NodeStatus, type INode } from './files/node';
|
|
15
|
-
export
|
|
15
|
+
export * from './utils/filename-validation';
|
|
16
|
+
export { getUniqueName } from './utils/filename';
|
|
16
17
|
export { formatFileSize, parseFileSize } from './utils/fileSize';
|
|
17
18
|
export { orderBy } from './utils/sorting';
|
|
18
19
|
export { sortNodes, FilesSortingMode, type FilesSortingOptions } from './utils/fileSorting';
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { generateRemoteUrl } from "@nextcloud/router";
|
|
|
6
6
|
import { CancelablePromise } from "cancelable-promise";
|
|
7
7
|
import { createClient, getPatcher } from "webdav";
|
|
8
8
|
import { isPublicShare, getSharingToken } from "@nextcloud/sharing/public";
|
|
9
|
+
import { getCapabilities } from "@nextcloud/capabilities";
|
|
9
10
|
import { getCanonicalLocale, getLanguage } from "@nextcloud/l10n";
|
|
10
11
|
import { TypedEventTarget } from "typescript-event-target";
|
|
11
12
|
const logger = getLoggerBuilder().setApp("@nextcloud/files").detectUser().build();
|
|
@@ -428,6 +429,9 @@ const validateData = (data, davService) => {
|
|
|
428
429
|
if (!data.source.startsWith("http")) {
|
|
429
430
|
throw new Error("Invalid source format, only http(s) is supported");
|
|
430
431
|
}
|
|
432
|
+
if (data.displayname && typeof data.displayname !== "string") {
|
|
433
|
+
throw new Error("Invalid displayname type");
|
|
434
|
+
}
|
|
431
435
|
if (data.mtime && !(data.mtime instanceof Date)) {
|
|
432
436
|
throw new Error("Invalid mtime type");
|
|
433
437
|
}
|
|
@@ -504,7 +508,12 @@ class Node {
|
|
|
504
508
|
};
|
|
505
509
|
constructor(data, davService) {
|
|
506
510
|
validateData(data, davService || this._knownDavService);
|
|
507
|
-
this._data = {
|
|
511
|
+
this._data = {
|
|
512
|
+
// TODO: Remove with next major release, this is just for compatibility
|
|
513
|
+
displayname: data.attributes?.displayname,
|
|
514
|
+
...data,
|
|
515
|
+
attributes: {}
|
|
516
|
+
};
|
|
508
517
|
this._attributes = new Proxy(this._data.attributes, this.handler);
|
|
509
518
|
this.update(data.attributes ?? {});
|
|
510
519
|
if (davService) {
|
|
@@ -534,6 +543,21 @@ class Node {
|
|
|
534
543
|
get basename() {
|
|
535
544
|
return basename(this.source);
|
|
536
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* The nodes displayname
|
|
548
|
+
* By default the display name and the `basename` are identical,
|
|
549
|
+
* but it is possible to have a different name. This happens
|
|
550
|
+
* on the files app for example for shared folders.
|
|
551
|
+
*/
|
|
552
|
+
get displayname() {
|
|
553
|
+
return this._data.displayname || this.basename;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Set the displayname
|
|
557
|
+
*/
|
|
558
|
+
set displayname(displayname) {
|
|
559
|
+
this._data.displayname = displayname;
|
|
560
|
+
}
|
|
537
561
|
/**
|
|
538
562
|
* Get this object's extension
|
|
539
563
|
* There is no setter as the source is not meant to be changed manually.
|
|
@@ -696,7 +720,11 @@ class Node {
|
|
|
696
720
|
*/
|
|
697
721
|
move(destination) {
|
|
698
722
|
validateData({ ...this._data, source: destination }, this._knownDavService);
|
|
723
|
+
const oldBasename = this.basename;
|
|
699
724
|
this._data.source = destination;
|
|
725
|
+
if (this.displayname === oldBasename && this.basename !== oldBasename) {
|
|
726
|
+
this.displayname = this.basename;
|
|
727
|
+
}
|
|
700
728
|
this.updateMtime();
|
|
701
729
|
}
|
|
702
730
|
/**
|
|
@@ -841,6 +869,7 @@ const davResultToNode = function(node, filesRoot = davRootPath, remoteURL = davR
|
|
|
841
869
|
source: `${remoteURL}${node.filename}`,
|
|
842
870
|
mtime: new Date(Date.parse(node.lastmod)),
|
|
843
871
|
mime: node.mime || "application/octet-stream",
|
|
872
|
+
displayname: props.displayname,
|
|
844
873
|
size: props?.size || Number.parseInt(props.getcontentlength || "0"),
|
|
845
874
|
// The fileid is set to -1 for failed requests
|
|
846
875
|
status: id < 0 ? NodeStatus.FAILED : void 0,
|
|
@@ -856,16 +885,81 @@ const davResultToNode = function(node, filesRoot = davRootPath, remoteURL = davR
|
|
|
856
885
|
delete nodeData.attributes?.props;
|
|
857
886
|
return node.type === "file" ? new File(nodeData) : new Folder(nodeData);
|
|
858
887
|
};
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
888
|
+
var InvalidFilenameErrorReason = /* @__PURE__ */ ((InvalidFilenameErrorReason2) => {
|
|
889
|
+
InvalidFilenameErrorReason2["ReservedName"] = "reserved name";
|
|
890
|
+
InvalidFilenameErrorReason2["Character"] = "character";
|
|
891
|
+
InvalidFilenameErrorReason2["Extension"] = "extension";
|
|
892
|
+
return InvalidFilenameErrorReason2;
|
|
893
|
+
})(InvalidFilenameErrorReason || {});
|
|
894
|
+
class InvalidFilenameError extends Error {
|
|
895
|
+
constructor(options) {
|
|
896
|
+
super(`Invalid ${options.reason} '${options.segment}' in filename '${options.filename}'`, { cause: options });
|
|
864
897
|
}
|
|
865
|
-
|
|
866
|
-
|
|
898
|
+
/**
|
|
899
|
+
* The filename that was validated
|
|
900
|
+
*/
|
|
901
|
+
get filename() {
|
|
902
|
+
return this.cause.filename;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Reason why the validation failed
|
|
906
|
+
*/
|
|
907
|
+
get reason() {
|
|
908
|
+
return this.cause.reason;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Part of the filename that caused this error
|
|
912
|
+
*/
|
|
913
|
+
get segment() {
|
|
914
|
+
return this.cause.segment;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function validateFilename(filename) {
|
|
918
|
+
const capabilities = getCapabilities().files;
|
|
919
|
+
const forbiddenCharacters = capabilities.forbidden_filename_characters ?? window._oc_config?.forbidden_filenames_characters ?? ["/", "\\"];
|
|
920
|
+
for (const character of forbiddenCharacters) {
|
|
921
|
+
if (filename.includes(character)) {
|
|
922
|
+
throw new InvalidFilenameError({ segment: character, reason: "character", filename });
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
filename = filename.toLocaleLowerCase();
|
|
926
|
+
const forbiddenFilenames = capabilities.forbidden_filenames ?? [".htaccess"];
|
|
927
|
+
if (forbiddenFilenames.includes(filename)) {
|
|
928
|
+
throw new InvalidFilenameError({
|
|
929
|
+
filename,
|
|
930
|
+
segment: filename,
|
|
931
|
+
reason: "reserved name"
|
|
932
|
+
/* ReservedName */
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
const endOfBasename = filename.indexOf(".", 1);
|
|
936
|
+
const basename2 = filename.substring(0, endOfBasename === -1 ? void 0 : endOfBasename);
|
|
937
|
+
const forbiddenFilenameBasenames = capabilities.forbidden_filename_basenames ?? [];
|
|
938
|
+
if (forbiddenFilenameBasenames.includes(basename2)) {
|
|
939
|
+
throw new InvalidFilenameError({
|
|
940
|
+
filename,
|
|
941
|
+
segment: basename2,
|
|
942
|
+
reason: "reserved name"
|
|
943
|
+
/* ReservedName */
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
const forbiddenFilenameExtensions = capabilities.forbidden_filename_extensions ?? [".part", ".filepart"];
|
|
947
|
+
for (const extension of forbiddenFilenameExtensions) {
|
|
948
|
+
if (filename.length > extension.length && filename.endsWith(extension)) {
|
|
949
|
+
throw new InvalidFilenameError({ segment: extension, reason: "extension", filename });
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function isFilenameValid(filename) {
|
|
954
|
+
try {
|
|
955
|
+
validateFilename(filename);
|
|
956
|
+
return true;
|
|
957
|
+
} catch (error) {
|
|
958
|
+
if (error instanceof InvalidFilenameError) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
throw error;
|
|
867
962
|
}
|
|
868
|
-
return true;
|
|
869
963
|
}
|
|
870
964
|
function getUniqueName(name, otherNames, options) {
|
|
871
965
|
const opts = {
|
|
@@ -2812,6 +2906,8 @@ export {
|
|
|
2812
2906
|
FilesSortingMode,
|
|
2813
2907
|
Folder,
|
|
2814
2908
|
Header,
|
|
2909
|
+
InvalidFilenameError,
|
|
2910
|
+
InvalidFilenameErrorReason,
|
|
2815
2911
|
Navigation,
|
|
2816
2912
|
NewMenuEntryCategory,
|
|
2817
2913
|
Node,
|
|
@@ -2847,5 +2943,6 @@ export {
|
|
|
2847
2943
|
registerFileAction,
|
|
2848
2944
|
registerFileListHeaders,
|
|
2849
2945
|
removeNewFileMenuEntry,
|
|
2850
|
-
sortNodes
|
|
2946
|
+
sortNodes,
|
|
2947
|
+
validateFilename
|
|
2851
2948
|
};
|