saltcorn-samba 0.4.6 → 0.4.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/index.js +34 -47
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,58 @@ All notable changes to `saltcorn-samba` are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.4.8] – 2026-07-05
8
+
9
+ ### Fixed – **Basispfad wurde beim Verbindungstest doppelt vorangestellt (die eigentliche Ursache)**
10
+
11
+ In allen Vorgängerversionen (0.4.4 – 0.4.7) hat der Verbindungstest den
12
+ konfigurierten Basispfad **zweimal** auf den Server geschickt und deshalb
13
+ systematisch die Meldung *„Der Basispfad ist auf dem Server nicht auffindbar"*
14
+ produziert — auch dann, wenn der Pfad in Wirklichkeit existierte und mit
15
+ `smbclient` oder dem Windows-Explorer problemlos erreichbar war.
16
+
17
+ **Ursache:** `buildClient(config)` speichert `config.base_path` intern als
18
+ `client.basePath`. Der Wrapper-`resolvePath(rel)` baut daraus
19
+ `<share>/<basePath>/<rel>`. Die Test-Route rief aber
20
+ `client.readdir(testCfg.base_path)` auf und übergab den Basispfad **noch
21
+ einmal** als relatives Argument. Ergebnis: aus `base_path = "static"` wurde
22
+ auf der Leitung `buero/static/static` — den es natürlich nicht gibt. Die
23
+ Diagnose-Ausgabe (`tested_path: static/static`) zeigte den Bug bereits
24
+ klar an, wurde aber bisher als Symptom statt Ursache gelesen.
25
+
26
+ **Fix:** Die Test-Route ruft jetzt `client.readdir("")` auf. Der Wrapper
27
+ resolvet das intern korrekt zu `<share>/<basePath>` — also genau dem Pfad,
28
+ den der Benutzer im Formular eingetragen hat. Für Anzeige-Zwecke (Meldungen,
29
+ Diagnose-Kacheln, Fallback-Probes) wird der Basispfad separat in
30
+ `baseForDisplay` gehalten.
31
+
32
+ ### Added – **Standalone-Diagnose-Skript `tools/diag-basepath.js`**
33
+
34
+ Wer den Verbindungsproblemen auf den Grund gehen will, kann jetzt außerhalb
35
+ von Saltcorn probieren, was smb3-client tatsächlich sieht:
36
+
37
+ ```bash
38
+ node tools/diag-basepath.js \
39
+ --host 192.168.110.10 --share buero --path static \
40
+ --user 01_vassen --domain buero.ib-vassen.de --password ...
41
+ ```
42
+
43
+ Das Skript führt sechs Probes durch (Share-Root, Share-Root mit Slash,
44
+ `stat` auf Ziel, `readdir` auf Ziel, Groß-/Kleinschreibvariante) und
45
+ druckt für jede den NT-Status-Code — hilfreich beim Aufspüren von
46
+ Schreibvarianten oder ACL-Problemen.
47
+
48
+ ## [0.4.7] – 2026-07-05
49
+
50
+ ### Fixed – **Irreführende Windows-UNC-Anzeige in der Fehlermeldung**
51
+
52
+ Die v0.4.6-Fehlermeldung zeigte den getesteten Pfad als Windows-UNC
53
+ (`\\\\192.168.110.10\\buero\\static`). Das war irreführend, weil das
54
+ Plugin an smb3-client tatsächlich Forward-Slash-Pfade schickt
55
+ (`buero/static`) — die Backslash-Anzeige suggerierte einen Bug, der
56
+ keiner ist. Jetzt wird der Pfad in genau der Form gezeigt, die auch
57
+ tatsächlich über die Leitung geht: `<share>/<basispfad>`.
58
+
7
59
  ## [0.4.6] – 2026-07-05
8
60
 
9
61
  ### Fixed – **Schreibvarianten-Test verdoppelte den Basispfad**
package/index.js CHANGED
@@ -853,72 +853,62 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
853
853
  // to stat("") — that already proves the whole handshake
854
854
  // (TCP + Negotiate + Session + TREE_CONNECT + Auth) works, which
855
855
  // is all the connection test actually promises.
856
- const rel = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
857
- // When a base_path is set, verify it exists and is a directory
858
- // BEFORE trying to enumerate it. This turns the opaque
859
- // "CREATE failed: STATUS_OBJECT_NAME_NOT_FOUND" into a clear
860
- // "Basispfad existiert nicht" hint the user can act on.
861
- // We used to run a stat() first. That turned out to be flaky:
862
- // smb3-client's stat() sends a CREATE with createOptions=0
863
- // (no directory hint) which some Samba configurations reject
864
- // for directories with strict ACLs. readdir() sends
865
- // createOptions=1 (DIRECTORY_FILE) and is the right primitive
866
- // for a base_path check anyway — we want to know the folder
867
- // can actually be listed, not just opened.
856
+ //
857
+ // IMPORTANT: buildClient already stores `base_path` as the
858
+ // wrapper's `basePath`, and wrapper.readdir(x) resolves to
859
+ // `share/basePath/x`. So we call readdir("") to list the
860
+ // configured basePath itself not readdir(base_path), which
861
+ // would append it twice (bug fixed in 0.4.8).
862
+ const rel = "";
863
+ const baseForDisplay = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
868
864
  try {
869
865
  return await client.readdir(rel);
870
866
  } catch (err) {
871
867
  // Look at both the wrapper error and its underlying cause.
872
868
  const causeMsg = String((err && err.cause && err.cause.message) || "");
873
869
  const msg = String((err && err.message) || err || "") + " " + causeMsg;
874
- const isRootProbe = !rel;
870
+ // The path we effectively asked the server to enumerate is
871
+ // `share/basePath` (rel is always "" here — basePath is the
872
+ // subdirectory to test).
873
+ const effectivePath = testCfg.share + (baseForDisplay ? "/" + baseForDisplay : "");
874
+ const isRootProbe = !baseForDisplay;
875
875
  const isRootEnumBug = /0xC0000033|OBJECT_NAME_INVALID|Share-Root/i.test(msg);
876
876
  if (isRootProbe && isRootEnumBug) {
877
- // Fall back: proof-of-life via stat on share root. This
878
- // confirms TCP + Negotiate + Session + Auth + TREE_CONNECT
879
- // without hitting the broken QUERY_DIRECTORY path.
877
+ // Share-root enumeration is a known smb3-client-vs-Samba
878
+ // limitation. Fall back to stat("") which proves the whole
879
+ // handshake works even if we can't list the root.
880
880
  try {
881
881
  await client.stat("");
882
- // Signal to the caller that the connection works, but
883
- // the share root cannot be enumerated on this server.
884
882
  const marker = [];
885
883
  marker._rootNotEnumerable = true;
886
884
  return marker;
887
885
  } catch (statErr) {
888
- // stat also failed — surface the original error.
889
886
  throw err;
890
887
  }
891
888
  }
892
- // A non-existent base_path (or one hidden from this user by
893
- // Samba's "hide unreadable" behaviour) surfaces here. Try to
894
- // gather actionable diagnostics: list the parent directory
895
- // (if not the share root) and probe common alternative
896
- // spellings of the missing segment so we can distinguish
897
- // typo / case-mismatch / permission problems.
889
+ // NAME_NOT_FOUND / PATH_NOT_FOUND on a configured base_path.
890
+ // Gather diagnostics: raw probes against alternate spellings
891
+ // and (if possible) a listing of the parent directory.
898
892
  const isMissing = /OBJECT_NAME_NOT_FOUND|OBJECT_PATH_NOT_FOUND|ENOENT|STATUS_NO_SUCH_FILE|existiert.*nicht/i.test(msg);
899
- if (rel && isMissing) {
900
- const parts = rel.split("/").filter(Boolean);
893
+ if (baseForDisplay && isMissing) {
894
+ const parts = baseForDisplay.split("/").filter(Boolean);
901
895
  const missing = parts[parts.length - 1];
902
896
  const parent = parts.slice(0, -1).join("/");
903
897
  const parentAbs = parent || "(Share-Root)";
904
- // We must probe via the *raw* smb3-client because the
905
- // wrapper's readdir() automatically prepends basePath
906
- // that would double up the segment we're trying to test
907
- // (e.g. readdir("static") → "buero/static/static").
898
+ // Probe via the raw smb3-client so we bypass the wrapper's
899
+ // basePath prefix and can test siblings/alternate spellings
900
+ // directly from the share root.
908
901
  const raw = client._raw;
909
902
  const rawShare = client.shareName;
910
903
  async function rawReaddir(relFromShareRoot) {
911
- const full = [rawShare, relFromShareRoot].filter(Boolean).join("/");
904
+ const full = relFromShareRoot ? rawShare + "/" + relFromShareRoot : rawShare;
912
905
  return await raw.readdir(full, { withFileTypes: true });
913
906
  }
914
907
  let siblings = null;
915
908
  let parent_error = null;
916
909
  if (parent) {
917
910
  try {
918
- // parent here is relative to basePath — but we want
919
- // absolute-from-share-root paths in the raw call.
920
- const parentFromShare = [client.basePath, parent].filter(Boolean).join("/");
921
- const listing = await rawReaddir(parentFromShare);
911
+ const listing = await rawReaddir(parent);
922
912
  siblings = Array.isArray(listing)
923
913
  ? listing.map((d) => ({
924
914
  name: d && d.name,
@@ -929,9 +919,7 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
929
919
  parent_error = String((parentErr && parentErr.message) || parentErr || "");
930
920
  }
931
921
  }
932
- // Probe alternate spellings: original, upper, lower, capitalised.
933
- // This lets us report "Ordner existiert unter anderem Namen" if
934
- // Samba is running with case sensitive = yes / case-preserved.
922
+ // Probe alternate spellings of the missing segment.
935
923
  const probes = [];
936
924
  const seen = new Set();
937
925
  const addProbe = (name) => {
@@ -945,9 +933,9 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
945
933
  addProbe(missing.charAt(0).toUpperCase() + missing.slice(1).toLowerCase());
946
934
  const probe_results = [];
947
935
  for (const p of probes) {
948
- // Build the *from-share-root* relative path so the probe
949
- // does not accidentally get basePath prepended twice.
950
- const relFromShare = [client.basePath, parent, p].filter(Boolean).join("/");
936
+ // From-share-root path: `parent/candidate`. NEVER include
937
+ // client.basePath here the raw client is share-relative.
938
+ const relFromShare = [parent, p].filter(Boolean).join("/");
951
939
  let ok = false;
952
940
  let perr = null;
953
941
  try {
@@ -961,7 +949,6 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
961
949
  const workingAlt = probe_results.find(
962
950
  (r) => r.ok && r.candidate !== missing
963
951
  );
964
- const testedPath = "\\\\" + testCfg.server + "\\" + testCfg.share + "\\" + rel.replace(/\//g, "\\");
965
952
  let hintText;
966
953
  if (workingAlt) {
967
954
  hintText =
@@ -972,11 +959,11 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
972
959
  "case-sensitive (\u201ecase sensitive = yes\u201c in smb.conf).";
973
960
  } else {
974
961
  hintText =
975
- "Der Basispfad \u201e" + rel + "\u201c wurde auf der " +
962
+ "Der Basispfad \u201e" + baseForDisplay + "\u201c wurde auf der " +
976
963
  "Freigabe \u201e" + testCfg.share + "\u201c nicht " +
977
- "gefunden. Getestet wurde der Pfad: " + testedPath + ". " +
978
- "M\u00f6gliche Ursachen: (a) Ordner existiert unter genau " +
979
- "diesem Namen nicht in dieser Freigabe (Basispfad ist " +
964
+ "gefunden. Intern getestet wurde: \u201e" + effectivePath +
965
+ "\u201c. M\u00f6gliche Ursachen: (a) Ordner existiert unter " +
966
+ "genau diesem Namen nicht in dieser Freigabe (Basispfad ist " +
980
967
  "relativ zur Freigabe \u2014 also z.\u202fB. \u201eunterordner\u201c, " +
981
968
  "nicht \u201e" + testCfg.share + "/unterordner\u201c); " +
982
969
  "(b) der angemeldete Benutzer \u201e" +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saltcorn-samba",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share via SMB 3.1.1 (AES-CMAC signing, optional encryption). File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
5
5
  "main": "index.js",
6
6
  "scripts": {