@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/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 { isFilenameValid, getUniqueName } from './utils/filename';
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 = { ...data, attributes: {} };
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
- const forbiddenCharacters = window._oc_config?.forbidden_filenames_characters ?? ["/", "\\"];
860
- const forbiddenFilenameRegex = window._oc_config?.blacklist_files_regex ? new RegExp(window._oc_config.blacklist_files_regex) : null;
861
- function isFilenameValid(filename) {
862
- if (forbiddenCharacters.some((character) => filename.includes(character))) {
863
- return false;
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
- if (forbiddenFilenameRegex !== null && filename.match(forbiddenFilenameRegex)) {
866
- return false;
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
  };