@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/README.md
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
# @nextcloud/files
|
|
6
6
|
[](https://www.npmjs.com/package/@nextcloud/files) [](https://api.reuse.software/info/github.com/nextcloud-libraries/nextcloud-files) [](https://app.codecov.io/gh/nextcloud-libraries/nextcloud-files) [](https://nextcloud-libraries.github.io/nextcloud-files/)
|
|
7
7
|
|
|
8
|
-
Nextcloud Files helpers for Nextcloud apps and libraries
|
|
8
|
+
Nextcloud Files helpers for Nextcloud apps and libraries.
|
|
9
|
+
|
|
10
|
+
The `davGetClient` exported function returns a webDAV client that's a wrapper around [webdav's webDAV client](https://www.npmjs.com/package/webdav); All its methods are available here.
|
|
9
11
|
|
|
10
12
|
## Usage example
|
|
11
13
|
|
|
@@ -57,3 +59,18 @@ const nodes = results.data.map((result) => davResultToNode(r, myRoot))
|
|
|
57
59
|
const nodes = results.data.map((result) => davResultToNode(r, myRoot, myRemoteURL))
|
|
58
60
|
|
|
59
61
|
```
|
|
62
|
+
|
|
63
|
+
### Using WebDAV to get a Node from a file's name
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { davGetClient, davGetDefaultPropfind, davResultToNode, davRootPath } from '@nextcloud/files'
|
|
67
|
+
import { emit } from '@nextcloud/event-bus'
|
|
68
|
+
const client = davGetClient()
|
|
69
|
+
client.stat(`${davRootPath}${filename}`, {
|
|
70
|
+
details: true,
|
|
71
|
+
data: davGetDefaultPropfind(),
|
|
72
|
+
}).then((result) => {
|
|
73
|
+
const node = davResultToNode(result.data)
|
|
74
|
+
emit('files:node:updated', node)
|
|
75
|
+
})
|
|
76
|
+
```
|
package/dist/files/node.d.ts
CHANGED
|
@@ -35,6 +35,17 @@ export declare abstract class Node {
|
|
|
35
35
|
* You can use the rename or move method to change the source.
|
|
36
36
|
*/
|
|
37
37
|
get basename(): string;
|
|
38
|
+
/**
|
|
39
|
+
* The nodes displayname
|
|
40
|
+
* By default the display name and the `basename` are identical,
|
|
41
|
+
* but it is possible to have a different name. This happens
|
|
42
|
+
* on the files app for example for shared folders.
|
|
43
|
+
*/
|
|
44
|
+
get displayname(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Set the displayname
|
|
47
|
+
*/
|
|
48
|
+
set displayname(displayname: string);
|
|
38
49
|
/**
|
|
39
50
|
* Get this object's extension
|
|
40
51
|
* There is no setter as the source is not meant to be changed manually.
|
package/dist/files/nodeData.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ const router = require("@nextcloud/router");
|
|
|
8
8
|
const cancelablePromise = require("cancelable-promise");
|
|
9
9
|
const webdav = require("webdav");
|
|
10
10
|
const _public = require("@nextcloud/sharing/public");
|
|
11
|
+
const capabilities = require("@nextcloud/capabilities");
|
|
11
12
|
const l10n = require("@nextcloud/l10n");
|
|
12
13
|
const typescriptEventTarget = require("typescript-event-target");
|
|
13
14
|
const logger = logger$1.getLoggerBuilder().setApp("@nextcloud/files").detectUser().build();
|
|
@@ -430,6 +431,9 @@ const validateData = (data, davService) => {
|
|
|
430
431
|
if (!data.source.startsWith("http")) {
|
|
431
432
|
throw new Error("Invalid source format, only http(s) is supported");
|
|
432
433
|
}
|
|
434
|
+
if (data.displayname && typeof data.displayname !== "string") {
|
|
435
|
+
throw new Error("Invalid displayname type");
|
|
436
|
+
}
|
|
433
437
|
if (data.mtime && !(data.mtime instanceof Date)) {
|
|
434
438
|
throw new Error("Invalid mtime type");
|
|
435
439
|
}
|
|
@@ -506,7 +510,12 @@ class Node {
|
|
|
506
510
|
};
|
|
507
511
|
constructor(data, davService) {
|
|
508
512
|
validateData(data, davService || this._knownDavService);
|
|
509
|
-
this._data = {
|
|
513
|
+
this._data = {
|
|
514
|
+
// TODO: Remove with next major release, this is just for compatibility
|
|
515
|
+
displayname: data.attributes?.displayname,
|
|
516
|
+
...data,
|
|
517
|
+
attributes: {}
|
|
518
|
+
};
|
|
510
519
|
this._attributes = new Proxy(this._data.attributes, this.handler);
|
|
511
520
|
this.update(data.attributes ?? {});
|
|
512
521
|
if (davService) {
|
|
@@ -536,6 +545,21 @@ class Node {
|
|
|
536
545
|
get basename() {
|
|
537
546
|
return path.basename(this.source);
|
|
538
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* The nodes displayname
|
|
550
|
+
* By default the display name and the `basename` are identical,
|
|
551
|
+
* but it is possible to have a different name. This happens
|
|
552
|
+
* on the files app for example for shared folders.
|
|
553
|
+
*/
|
|
554
|
+
get displayname() {
|
|
555
|
+
return this._data.displayname || this.basename;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Set the displayname
|
|
559
|
+
*/
|
|
560
|
+
set displayname(displayname) {
|
|
561
|
+
this._data.displayname = displayname;
|
|
562
|
+
}
|
|
539
563
|
/**
|
|
540
564
|
* Get this object's extension
|
|
541
565
|
* There is no setter as the source is not meant to be changed manually.
|
|
@@ -698,7 +722,11 @@ class Node {
|
|
|
698
722
|
*/
|
|
699
723
|
move(destination) {
|
|
700
724
|
validateData({ ...this._data, source: destination }, this._knownDavService);
|
|
725
|
+
const oldBasename = this.basename;
|
|
701
726
|
this._data.source = destination;
|
|
727
|
+
if (this.displayname === oldBasename && this.basename !== oldBasename) {
|
|
728
|
+
this.displayname = this.basename;
|
|
729
|
+
}
|
|
702
730
|
this.updateMtime();
|
|
703
731
|
}
|
|
704
732
|
/**
|
|
@@ -843,6 +871,7 @@ const davResultToNode = function(node, filesRoot = davRootPath, remoteURL = davR
|
|
|
843
871
|
source: `${remoteURL}${node.filename}`,
|
|
844
872
|
mtime: new Date(Date.parse(node.lastmod)),
|
|
845
873
|
mime: node.mime || "application/octet-stream",
|
|
874
|
+
displayname: props.displayname,
|
|
846
875
|
size: props?.size || Number.parseInt(props.getcontentlength || "0"),
|
|
847
876
|
// The fileid is set to -1 for failed requests
|
|
848
877
|
status: id < 0 ? NodeStatus.FAILED : void 0,
|
|
@@ -858,16 +887,81 @@ const davResultToNode = function(node, filesRoot = davRootPath, remoteURL = davR
|
|
|
858
887
|
delete nodeData.attributes?.props;
|
|
859
888
|
return node.type === "file" ? new File(nodeData) : new Folder(nodeData);
|
|
860
889
|
};
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
890
|
+
var InvalidFilenameErrorReason = /* @__PURE__ */ ((InvalidFilenameErrorReason2) => {
|
|
891
|
+
InvalidFilenameErrorReason2["ReservedName"] = "reserved name";
|
|
892
|
+
InvalidFilenameErrorReason2["Character"] = "character";
|
|
893
|
+
InvalidFilenameErrorReason2["Extension"] = "extension";
|
|
894
|
+
return InvalidFilenameErrorReason2;
|
|
895
|
+
})(InvalidFilenameErrorReason || {});
|
|
896
|
+
class InvalidFilenameError extends Error {
|
|
897
|
+
constructor(options) {
|
|
898
|
+
super(`Invalid ${options.reason} '${options.segment}' in filename '${options.filename}'`, { cause: options });
|
|
866
899
|
}
|
|
867
|
-
|
|
868
|
-
|
|
900
|
+
/**
|
|
901
|
+
* The filename that was validated
|
|
902
|
+
*/
|
|
903
|
+
get filename() {
|
|
904
|
+
return this.cause.filename;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Reason why the validation failed
|
|
908
|
+
*/
|
|
909
|
+
get reason() {
|
|
910
|
+
return this.cause.reason;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Part of the filename that caused this error
|
|
914
|
+
*/
|
|
915
|
+
get segment() {
|
|
916
|
+
return this.cause.segment;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function validateFilename(filename) {
|
|
920
|
+
const capabilities$1 = capabilities.getCapabilities().files;
|
|
921
|
+
const forbiddenCharacters = capabilities$1.forbidden_filename_characters ?? window._oc_config?.forbidden_filenames_characters ?? ["/", "\\"];
|
|
922
|
+
for (const character of forbiddenCharacters) {
|
|
923
|
+
if (filename.includes(character)) {
|
|
924
|
+
throw new InvalidFilenameError({ segment: character, reason: "character", filename });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
filename = filename.toLocaleLowerCase();
|
|
928
|
+
const forbiddenFilenames = capabilities$1.forbidden_filenames ?? [".htaccess"];
|
|
929
|
+
if (forbiddenFilenames.includes(filename)) {
|
|
930
|
+
throw new InvalidFilenameError({
|
|
931
|
+
filename,
|
|
932
|
+
segment: filename,
|
|
933
|
+
reason: "reserved name"
|
|
934
|
+
/* ReservedName */
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
const endOfBasename = filename.indexOf(".", 1);
|
|
938
|
+
const basename = filename.substring(0, endOfBasename === -1 ? void 0 : endOfBasename);
|
|
939
|
+
const forbiddenFilenameBasenames = capabilities$1.forbidden_filename_basenames ?? [];
|
|
940
|
+
if (forbiddenFilenameBasenames.includes(basename)) {
|
|
941
|
+
throw new InvalidFilenameError({
|
|
942
|
+
filename,
|
|
943
|
+
segment: basename,
|
|
944
|
+
reason: "reserved name"
|
|
945
|
+
/* ReservedName */
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
const forbiddenFilenameExtensions = capabilities$1.forbidden_filename_extensions ?? [".part", ".filepart"];
|
|
949
|
+
for (const extension of forbiddenFilenameExtensions) {
|
|
950
|
+
if (filename.length > extension.length && filename.endsWith(extension)) {
|
|
951
|
+
throw new InvalidFilenameError({ segment: extension, reason: "extension", filename });
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function isFilenameValid(filename) {
|
|
956
|
+
try {
|
|
957
|
+
validateFilename(filename);
|
|
958
|
+
return true;
|
|
959
|
+
} catch (error) {
|
|
960
|
+
if (error instanceof InvalidFilenameError) {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
throw error;
|
|
869
964
|
}
|
|
870
|
-
return true;
|
|
871
965
|
}
|
|
872
966
|
function getUniqueName(name, otherNames, options) {
|
|
873
967
|
const opts = {
|
|
@@ -2813,6 +2907,8 @@ exports.FileType = FileType;
|
|
|
2813
2907
|
exports.FilesSortingMode = FilesSortingMode;
|
|
2814
2908
|
exports.Folder = Folder;
|
|
2815
2909
|
exports.Header = Header;
|
|
2910
|
+
exports.InvalidFilenameError = InvalidFilenameError;
|
|
2911
|
+
exports.InvalidFilenameErrorReason = InvalidFilenameErrorReason;
|
|
2816
2912
|
exports.Navigation = Navigation;
|
|
2817
2913
|
exports.NewMenuEntryCategory = NewMenuEntryCategory;
|
|
2818
2914
|
exports.Node = Node;
|
|
@@ -2849,3 +2945,4 @@ exports.registerFileAction = registerFileAction;
|
|
|
2849
2945
|
exports.registerFileListHeaders = registerFileListHeaders;
|
|
2850
2946
|
exports.removeNewFileMenuEntry = removeNewFileMenuEntry;
|
|
2851
2947
|
exports.sortNodes = sortNodes;
|
|
2948
|
+
exports.validateFilename = validateFilename;
|