Added ability to list share folders from the SMB server

This commit is contained in:
owendswang committed 2026-10-18 00:20:43 +08:00
1 parent e72bd6f1cf
commit f105808ee4
9 files changed
+240 -31

No files matched your search

+1 -1
View File
@@ -110,7 +110,7 @@ On startup the payload registers a `PS5 Web File Manager` web shortcut in the Me
- Delete is recursive and permanent. There is no recycle bin.
- Copy/move tasks can be canceled. A partially copied single file is removed, but partially copied folders are left in place to avoid deleting existing files when merging into an existing target folder.
- Upload tasks can be canceled. A partially uploaded temporary file is removed when possible.
- Downloading a folder or multiple selected items produces a tar stream. The tar archive is generated by the payload and is not written to PS5 storage first.
- Selected files download individually, and each selected folder produces its own tar stream. The tar archives are generated by the payload and are not written to PS5 storage first.
- The UI can recover the active task display if the browser is closed and reopened while the payload process is still running.
- Text editing is limited to common text-file extensions. Non-UTF-8 and oversized files are rejected.
- File names are transmitted as UTF-8 through the web API. The payload also attempts to preserve legacy byte-oriented names returned by mounted filesystems so mixed USB filename encodings can still be displayed and operated on.
+10 -1
View File
@@ -153,7 +153,16 @@
<section class="permission-panel smb-manager-panel" role="dialog" aria-modal="true" aria-labelledby="smbManagerTitle">
<h2 id="smbManagerTitle" class="permission-title" data-i18n="manageSmb"></h2>
<div id="smbConnectionList" class="smb-connection-list"></div>
<div class="permission-actions">
<div id="smbSharePicker" hidden>
<label for="smbShareSelect" data-i18n="smbChooseShare"></label>
<select id="smbShareSelect"></select>
<p class="smb-connections-empty" data-i18n="smbShareHint"></p>
<div class="permission-actions">
<button id="smbShareConnectBtn" class="primary" type="button" data-i18n="smbConnect"></button>
<button id="smbShareCancelBtn" type="button" data-i18n="cancel"></button>
</div>
</div>
<div id="smbManagerActions" class="permission-actions">
<button id="smbManagerAddBtn" class="primary" type="button" data-i18n="addSmb"></button>
<button id="smbManagerCloseBtn" type="button" data-i18n="close"></button>
</div>
+7 -1
View File
@@ -85,7 +85,13 @@ window.WFM_LANG = {
smbConnectionsEmpty: "No SMB connections yet. Add one below.",
smbRemoveConfirm: "Remove SMB connection \"{path}\"?\n\nFiles on the share will not be deleted.",
smbRemoveFailed: "Could not remove SMB connection: {error}",
smbAddressPrompt: "Enter an SMB address:\nsmb://server/share[/path]\nWith authentication: smb://username:password@server/share",
smbAddressPrompt: "Enter an SMB address:\nsmb://server/ (choose a share)\nsmb://server/share[/path]\nWith authentication: smb://username:password@server/share",
smbChooseShare: "Choose a shared folder",
smbConnect: "Connect",
smbShareHint: "Share missing? Cancel and enter its full SMB address.",
smbNoShares: "The server did not list any file shares. Try entering the full SMB address.",
err_smb_share_enum_failed: "Could not list shares. Try entering the full SMB address: {arg}",
err_smb_password_required: "Access denied. Check your credentials or enter the full SMB address: {arg}",
smbPasswordPrompt: "Enter the SMB password (saved only in this browser):\n{path}",
smbPasswordCanceled: "SMB password entry canceled",
smbConnectFailed: "Could not connect to SMB: {error}",
+7 -1
View File
@@ -85,7 +85,13 @@ window.WFM_LANG = {
smbConnectionsEmpty: "还没有 SMB 连接,可点击下方添加。",
smbRemoveConfirm: "移除 SMB 连接「{path}」?\n\n共享中的文件不会被删除。",
smbRemoveFailed: "移除 SMB 连接失败:{error}",
smbAddressPrompt: "输入 SMB 地址:\nsmb://服务器/共享目录[/路径]\n需要认证时:smb://用户名:密码@服务器/共享目录",
smbAddressPrompt: "输入 SMB 地址:\nsmb://服务器/(选择共享)\nsmb://服务器/共享目录[/路径]\n需要认证时:smb://用户名:密码@服务器/共享目录",
smbChooseShare: "选择共享目录",
smbConnect: "连接",
smbShareHint: "没有列出需要的共享?取消后可输入完整共享地址。",
smbNoShares: "服务器未列出文件共享,可尝试输入完整共享地址。",
err_smb_share_enum_failed: "无法列出共享,可尝试输入完整共享地址:{arg}",
err_smb_password_required: "服务器拒绝访问,请检查账号密码或输入完整共享地址:{arg}",
smbPasswordPrompt: "请输入 SMB 密码(仅保存在当前浏览器):\n{path}",
smbPasswordCanceled: "已取消输入 SMB 密码",
smbConnectFailed: "无法连接 SMB:{error}",
+28 -3
View File
@@ -424,6 +424,11 @@ button.install-action:hover:not(:disabled) {
pointer-events: none;
}
/* PS5 cursor navigation also targets scrollable containers behind overlays. */
.shell.modal-open .content {
overflow: hidden;
}
.content-loading {
position: absolute;
right: 0;
@@ -785,7 +790,7 @@ input[type="checkbox"] {
min-width: 40px;
height: 40px;
min-height: 40px;
padding: 9px;
padding: 8px;
border: 0;
border-radius: 6px;
background: transparent;
@@ -799,8 +804,8 @@ input[type="checkbox"] {
.row-tool img {
display: block;
width: 20px;
height: 20px;
width: 24px;
height: 24px;
}
@media (min-width: 641px) and (hover: none) {
@@ -809,6 +814,10 @@ input[type="checkbox"] {
}
}
.shell.interaction-locked .file-name-cell .row-tools {
display: none;
}
.empty {
color: #aab4be;
padding: 20px 90px;
@@ -1102,6 +1111,22 @@ input[type="checkbox"] {
flex: 0 0 auto;
}
#smbSharePicker {
margin-top: 20px;
}
#smbShareSelect {
display: block;
width: 100%;
margin-top: 12px;
padding: 8px 10px;
border: 1px solid #46515f;
border-radius: 6px;
background: #20262d;
color: #edf0f2;
font: inherit;
}
.permission-path {
display: flex;
margin: 10px 0 22px;
+98 -23
View File
@@ -20,6 +20,7 @@ let lastCompletionId = null;
let taskPollFailedAlertShown = false;
let taskPollPromise = null;
let localActionBusy = false;
let modalBackgroundLocked = false;
let specialStoragePaths = {};
let hasSpecialMntStorage = false;
let textEditorPath = null;
@@ -72,6 +73,10 @@ const smbManagerOverlayEl = document.getElementById("smbManagerOverlay");
const smbConnectionListEl = document.getElementById("smbConnectionList");
const smbManagerAddBtn = document.getElementById("smbManagerAddBtn");
const smbManagerCloseBtn = document.getElementById("smbManagerCloseBtn");
const smbSharePickerEl = document.getElementById("smbSharePicker");
const smbShareSelectEl = document.getElementById("smbShareSelect");
const smbManagerActionsEl = document.getElementById("smbManagerActions");
let smbShareChoice = null;
let smbConnections = [];
const statusEl = document.getElementById("statusText");
const versionEl = document.getElementById("versionText");
@@ -260,7 +265,7 @@ function hideContentLoading() {
clearTimeout(directoryLoadingTimer);
directoryLoadingTimer = 0;
directoryLoadingStartedAt = 0;
contentEl.classList.remove("loading");
contentEl.classList.toggle("loading", modalBackgroundLocked);
contentLoadingEl.hidden = true;
updateButtons();
}
@@ -625,17 +630,24 @@ function setBusy(value) {
}
function setModalBackgroundLocked(value) {
contentEl.classList.toggle("loading", value);
modalBackgroundLocked = value;
contentEl.classList.toggle("loading", value || !contentLoadingEl.hidden);
if (value) clearHoverPath();
updateButtons();
}
function backgroundInteractionLocked() {
return Boolean(modalBackgroundLocked || busy || loadingPath ||
contentEl.classList.contains("loading"));
}
function showTaskOverlay() {
setBusy(true);
if (!overlayEl.hidden || taskOverlayTimer) return;
taskOverlayTimer = setTimeout(() => {
taskOverlayTimer = 0;
overlayEl.hidden = false;
updateButtons();
}, TASK_OVERLAY_DISPLAY_DELAY);
}
@@ -643,6 +655,7 @@ function hideTaskOverlay() {
clearTimeout(taskOverlayTimer);
taskOverlayTimer = 0;
overlayEl.hidden = true;
document.querySelector(".shell").classList.toggle("modal-open", modalBackgroundLocked);
}
function showActionFailed(label, error) {
@@ -877,8 +890,7 @@ async function refreshSpaces() {
}
function spaceNavigationLocked() {
return Boolean(busy || loadingPath || !contentLoadingEl.hidden ||
contentEl.classList.contains("loading"));
return backgroundInteractionLocked() || !contentLoadingEl.hidden;
}
function readSmbPassword(item) {
@@ -986,6 +998,7 @@ function renderSmbConnections() {
function openSmbManager() {
if (spaceNavigationLocked()) return;
setModalBackgroundLocked(true);
smbManagerOverlayEl.hidden = false;
renderSmbConnections();
smbManagerAddBtn.focus();
@@ -993,13 +1006,19 @@ function openSmbManager() {
}
function closeSmbManager() {
if (smbShareChoice) { smbShareChoice(null); return; }
if (localActionBusy) return;
smbManagerOverlayEl.hidden = true;
setModalBackgroundLocked(false);
manageSmbBtn.focus();
}
function smbManagerBusy() {
return Boolean(busy || loadingPath || !contentLoadingEl.hidden);
}
async function removeSmbConnection(path) {
if (spaceNavigationLocked() || !confirm(t("smbRemoveConfirm", { path: displayPath(path) }))) return;
if (smbManagerBusy() || !confirm(t("smbRemoveConfirm", { path: displayPath(path) }))) return;
try {
localActionBusy = true;
setBusy(true);
@@ -1011,14 +1030,40 @@ async function removeSmbConnection(path) {
alert(t("smbRemoveFailed", { error: err.message }));
} finally {
localActionBusy = false;
setModalBackgroundLocked(!smbManagerOverlayEl.hidden);
setBusy(false);
renderSmbConnections();
smbManagerAddBtn.focus();
}
}
function chooseSmbShare(shares) {
smbShareSelectEl.textContent = "";
for (const rawName of shares) {
const name = decodeFsText(rawName);
const option = document.createElement("option");
option.value = name;
option.textContent = name;
smbShareSelectEl.appendChild(option);
}
smbConnectionListEl.hidden = true;
smbManagerActionsEl.hidden = true;
smbSharePickerEl.hidden = false;
smbShareSelectEl.focus();
return new Promise(resolve => {
smbShareChoice = value => {
smbShareChoice = null;
smbSharePickerEl.hidden = true;
smbShareSelectEl.textContent = "";
smbConnectionListEl.hidden = false;
smbManagerActionsEl.hidden = false;
resolve(value);
};
});
}
async function addSmbConnection() {
if (spaceNavigationLocked()) return;
if (smbManagerBusy()) return;
const address = prompt(t("smbAddressPrompt"), "smb://");
if (typeof address !== "string") return;
const input = address.trim();
@@ -1026,7 +1071,7 @@ async function addSmbConnection() {
try {
localActionBusy = true;
setBusy(true);
const authority = /^smb:\/\/([^/]+)\//.exec(input);
const authority = /^smb:\/\/([^/]+)(?:\/|$)/.exec(input);
let clean = input;
let password = "";
if (authority && authority[1].indexOf("@") >= 0) {
@@ -1036,6 +1081,20 @@ async function addSmbConnection() {
password = colon >= 0 ? decodeURIComponent(credentials.slice(colon + 1)) : askSmbPassword(input);
if (colon >= 0) clean = input.replace(authority[1], credentials.slice(0, colon) + "@" + authority[1].slice(at + 1));
}
if (/^smb:\/\/[^/]+\/?$/.test(clean)) {
let listing;
try {
listing = await apiForm("/api/smb/shares", { address: clean, password: password });
} catch (err) {
if (password || err.code !== "smb_password_required") throw err;
password = askSmbPassword(clean);
listing = await apiForm("/api/smb/shares", { address: clean, password: password });
}
if (!listing.shares.length) throw new Error(t("smbNoShares"));
const share = await chooseSmbShare(listing.shares);
if (share === null) return;
clean = clean.replace(/\/$/, "") + "/" + share;
}
const data = await apiForm("/api/smb/add", { address: clean, password: password });
saveSmbPassword(data, password);
smbReady[data.path] = Promise.resolve();
@@ -1046,8 +1105,10 @@ async function addSmbConnection() {
if (err.code !== "smb_password_canceled") alert(t("smbConnectFailed", { error: err.message }));
} finally {
localActionBusy = false;
setModalBackgroundLocked(!smbManagerOverlayEl.hidden);
setBusy(false);
if (smbManagerOverlayEl.hidden) manageSmbBtn.focus();
else smbManagerAddBtn.focus();
}
}
@@ -1751,7 +1812,7 @@ function renderTaskName(el, task) {
function renderClipboard() {
const hasClipboard = Boolean(clipboard && clipboard.items.length);
const locked = busy || contentEl.classList.contains("loading");
const locked = backgroundInteractionLocked();
pasteBtn.hidden = !hasClipboard;
clearClipboardBtn.hidden = !hasClipboard;
if (!hasClipboard) {
@@ -1805,12 +1866,16 @@ function singleSelected() {
function updateButtons() {
const items = selectedEntries();
const locked = busy || contentEl.classList.contains("loading");
const locked = backgroundInteractionLocked();
const shell = document.querySelector(".shell");
shell.classList.toggle("interaction-locked", locked);
shell.classList.toggle("modal-open", modalBackgroundLocked || !overlayEl.hidden);
versionEl.disabled = locked;
manageSmbBtn.disabled = spaceNavigationLocked();
smbManagerAddBtn.disabled = spaceNavigationLocked();
smbManagerAddBtn.disabled = smbManagerBusy();
smbManagerCloseBtn.disabled = localActionBusy;
for (const button of smbConnectionListEl.querySelectorAll("button")) {
button.disabled = spaceNavigationLocked();
button.disabled = smbManagerBusy();
}
for (const select of spaceInfoEl.querySelectorAll(".space-select")) {
select.disabled = spaceNavigationLocked();
@@ -1824,6 +1889,8 @@ function updateButtons() {
uploadBtn.disabled = locked;
uploadMenuBtn.disabled = locked;
uploadFolderBtn.disabled = locked;
uploadFilesEl.disabled = locked;
uploadFolderEl.disabled = locked;
document.getElementById("mkdirBtn").disabled = locked;
newTextBtn.disabled = locked;
for (const button of filesEl.querySelectorAll(".row-action, .mode-action, .row-tool")) {
@@ -1888,7 +1955,7 @@ function focusPath(path) {
}
function hoverRow(row) {
if (hoverPaused) return;
if (hoverPaused || backgroundInteractionLocked()) return;
if (hoveredRow === row) return;
clearHoverPath();
hoveredRow = row;
@@ -1974,7 +2041,7 @@ function clearSelection(updateVisibleRows) {
const FILE_ACTIONS = { download: actionDownload, copy: actionCopy, move: actionMove, rename: actionRename, delete: actionDelete };
function runRowAction(item, action) {
if (busy || loadingPath || contentEl.classList.contains("loading")) return;
if (backgroundInteractionLocked()) return;
clearSelection();
togglePath(item.path, true);
focusPath(item.path);
@@ -2732,7 +2799,7 @@ function uploadFileRequest(taskId, file, rel, overwrite, index) {
}
async function uploadFiles(files, relativeNames) {
if (busy || loadingPath || !files.length) return;
if (backgroundInteractionLocked() || !files.length) return;
const useLoading = files.length >= SELECT_ALL_LOADING_THRESHOLD;
let list;
let rels = [];
@@ -2887,12 +2954,12 @@ function setupDropUpload() {
if (!dataTransferHasFiles(event.dataTransfer)) return;
event.preventDefault();
dragDepth++;
if (!busy && !loadingPath) dropUploadOverlayEl.hidden = false;
if (!backgroundInteractionLocked()) dropUploadOverlayEl.hidden = false;
});
window.addEventListener("dragover", event => {
if (!dataTransferHasFiles(event.dataTransfer)) return;
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
event.dataTransfer.dropEffect = backgroundInteractionLocked() ? "none" : "copy";
});
window.addEventListener("dragleave", () => {
dragDepth = Math.max(0, dragDepth - 1);
@@ -2903,7 +2970,7 @@ function setupDropUpload() {
event.preventDefault();
dragDepth = 0;
dropUploadOverlayEl.hidden = true;
if (busy || loadingPath) return;
if (backgroundInteractionLocked()) return;
uploadDroppedItems(event.dataTransfer).catch(err => {
const message = t("uploadFailed", { error: err.message || t("backendError") });
setStatus(message);
@@ -2970,10 +3037,11 @@ A file manager for PS5 with a web UI. It is primarily intended for quickly and s
Credits:
ps5-payload-dev/websrv, ps5-payload-dev/ftpsrv,
seregonwar/zftpd, itsPLK/ps5-payload-manager,
libmicrohttpd, ps5-payload-dev/sdk, etaHEN, ezremote.
itsPLK/ps5-pkg-manager, libmicrohttpd, libsmb2,
ps5-payload-dev/sdk, etaHEN, ezremote.
License:
GPLv3 or later. Third-party licenses apply; libmicrohttpd is LGPL.`);
GPLv3 or later. Third-party licenses apply; libmicrohttpd and libsmb2 are LGPL.`);
});
parentBtn.addEventListener("click", actionParentDirectory);
nameFilterBtn.addEventListener("click", event => {
@@ -3004,6 +3072,12 @@ pkgInfoImageEl.addEventListener("error", () => {
manageSmbBtn.addEventListener("click", openSmbManager);
smbManagerAddBtn.addEventListener("click", addSmbConnection);
smbManagerCloseBtn.addEventListener("click", closeSmbManager);
document.getElementById("smbShareConnectBtn").addEventListener("click", () => {
if (smbShareChoice) smbShareChoice(smbShareSelectEl.value);
});
document.getElementById("smbShareCancelBtn").addEventListener("click", () => {
if (smbShareChoice) smbShareChoice(null);
});
smbManagerOverlayEl.addEventListener("click", event => {
if (event.target === smbManagerOverlayEl) closeSmbManager();
});
@@ -3071,7 +3145,8 @@ document.addEventListener("keydown", event => {
document.body.classList.add("keyboard-navigation");
}
if (!smbManagerOverlayEl.hidden && (event.key === "Tab" || event.keyCode === 9)) {
const buttons = smbManagerOverlayEl.querySelectorAll("button:not(:disabled)");
const buttons = Array.from(smbManagerOverlayEl.querySelectorAll("select, button:not(:disabled)"))
.filter(button => button.offsetParent !== null);
if (!buttons.length) { event.preventDefault(); return; }
const first = buttons[0];
const last = buttons[buttons.length - 1];
@@ -3120,8 +3195,8 @@ permissionOverlayEl.addEventListener("click", event => {
extractOverlayEl.addEventListener("click", event => {
if (event.target === extractOverlayEl) closeExtractDialog();
});
contentEl.addEventListener("click", event => {
if (!busy && !loadingPath) return;
document.querySelector(".shell").addEventListener("click", event => {
if (!backgroundInteractionLocked()) return;
event.preventDefault();
event.stopPropagation();
}, true);
@@ -3130,7 +3205,7 @@ filesEl.addEventListener("focusin", event => {
if (row) focusPath(row.dataset.path);
});
filesEl.addEventListener("change", event => {
if (busy || loadingPath) {
if (backgroundInteractionLocked()) {
event.preventDefault();
return;
}
+5
View File
@@ -1952,6 +1952,11 @@ filemgr_api_request(struct MHD_Connection *conn, const char *url,
if(!strcmp(url, "/api/list")) return api_list(conn);
if(!strcmp(url, "/api/tasks")) return api_tasks(conn);
if(!strcmp(url, "/api/space")) return api_space(conn);
if(!strcmp(url, "/api/smb/shares")) {
return strcmp(method, MHD_HTTP_METHOD_POST) ?
send_json_error(conn, MHD_HTTP_METHOD_NOT_ALLOWED, "invalid method") :
api_smb_shares(conn, body, body_size);
}
if(!strcmp(url, "/api/smb/add") || !strcmp(url, "/api/smb/connect")) {
return strcmp(method, MHD_HTTP_METHOD_POST) ?
send_json_error(conn, MHD_HTTP_METHOD_NOT_ALLOWED, "invalid method") :
+83 -1
View File
@@ -7,6 +7,7 @@
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -15,6 +16,9 @@
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <smb2/libsmb2-dcerpc-srvsvc.h>
#include <smb2/libsmb2-raw.h>
#include <smb2/smb2-errors.h>
#define SMB_CONNECTION_MAX 64
#if WFM_DEBUG
@@ -237,8 +241,11 @@ static struct smb2_context *connect_address(const smb_address_t *a) {
} else {
snprintf(client_error, sizeof(client_error), "%s", smb2_get_error(ctx));
}
uint32_t status = smb2_get_nterror(ctx);
int auth_error = status == SMB2_STATUS_LOGON_FAILURE ||
status == SMB2_STATUS_WRONG_PASSWORD || status == SMB2_STATUS_ACCESS_DENIED;
smb2_destroy_context(ctx);
errno = -result;
errno = auth_error ? EACCES : -result;
}
return NULL;
}
@@ -263,6 +270,81 @@ struct smb2_context *smb_connect_path(const char *path, smb_address_t *a) {
return connect_address(a);
}
typedef struct share_enum_result {
int done;
int status;
struct srvsvc_NetrShareEnum_rep *reply;
} share_enum_result_t;
static void share_enum_cb(struct smb2_context *ctx, int status, void *data, void *opaque) {
share_enum_result_t *result = opaque;
result->status = status;
result->reply = data;
result->done = 1;
}
enum MHD_Result api_smb_shares(struct MHD_Connection *conn, const char *body, size_t size) {
char *input = body_form_value(body, size, "address");
char *password = body_form_value(body, size, "password");
char ipc[PATH_MAX];
smb_address_t a;
size_t len = input ? strlen(input) : 0;
if(len && input[len - 1] == '/') input[--len] = 0;
int valid = input && smb_path(input) && !strchr(input + 6, '/') &&
snprintf(ipc, sizeof(ipc), "%s/IPC$", input) < (int)sizeof(ipc) &&
!smb_parse_address(ipc, &a);
free(input);
if(!valid || (password && copy_field(a.password, sizeof(a.password), password, strlen(password)))) {
free(password);
return send_json_error(conn, MHD_HTTP_BAD_REQUEST, "invalid SMB server address; use smb://server/");
}
free(password);
if(has_active_task()) return send_json_error(conn, MHD_HTTP_CONFLICT, "another task is running");
smb_lock();
struct smb2_context *ctx = connect_address(&a);
share_enum_result_t result = {0};
int status = ctx ? smb2_share_enum_async(ctx, SHARE_INFO_1, share_enum_cb, &result) : -errno;
struct timespec start, now;
clock_gettime(CLOCK_MONOTONIC, &start);
while(ctx && !status && !result.done) {
struct pollfd pfd = {.fd = smb2_get_fd(ctx), .events = smb2_which_events(ctx)};
int ready = poll(&pfd, 1, 1000);
if(ready < 0 && errno == EINTR) continue;
if(ready < 0) { status = -errno; break; }
status = smb2_service(ctx, pfd.revents);
clock_gettime(CLOCK_MONOTONIC, &now);
if(!result.done && now.tv_sec - start.tv_sec >= 15) status = -ETIMEDOUT;
}
if(!status) status = result.status;
if(!status && !result.reply) status = -EIO;
strbuf_t json = {0};
if(!status) {
strbuf_append(&json, "{\"ok\":true,\"shares\":[");
struct srvsvc_SHARE_INFO_1_CONTAINER *shares = &result.reply->ses.ShareInfo.Level1;
int first = 1;
for(uint32_t i = 0; shares->Buffer && i < shares->EntriesRead; i++) {
struct srvsvc_SHARE_INFO_1 *share = &shares->Buffer->share_info_1[i];
if((share->type & 3) != SHARE_TYPE_DISKTREE || !share->netname.utf8) continue;
if(!first) strbuf_append(&json, ",");
first = 0;
json_escape(&json, share->netname.utf8);
}
strbuf_append(&json, "]}");
}
char detail[512];
snprintf(detail, sizeof(detail), "%s", ctx ? smb2_get_error(ctx) : client_error);
if(status && !detail[0]) snprintf(detail, sizeof(detail), "share enumeration failed (%d)", status);
SMB_DEBUG("share enumeration server=%s status=%d detail=%s", a.server, status, detail);
int password_required = status == -EACCES || status == 5;
if(result.reply) smb2_free_data(ctx, result.reply);
/* Keep callback state alive until outstanding requests have been canceled. */
if(ctx) smb2_destroy_context(ctx);
smb_unlock();
if(status) return send_json_error_detail(conn, MHD_HTTP_BAD_GATEWAY, "SMB share enumeration failed",
password_required ? "smb_password_required" : "smb_share_enum_failed", detail);
return send_buffer(conn, MHD_HTTP_OK, json.data, "application/json");
}
enum MHD_Result api_smb_add(struct MHD_Connection *conn, const char *body, size_t size, int connecting) {
char *input = body_form_value(body, size, "address");
char *password = body_form_value(body, size, "password");
+1
View File
@@ -26,6 +26,7 @@ void smb_unlock(void);
struct smb2_context *smb_connect_path(const char *path, smb_address_t *address);
int smb_result(int result);
void smb_append_spaces(strbuf_t *json, const char *current, int *first);
enum MHD_Result api_smb_shares(struct MHD_Connection *conn, const char *body, size_t size);
enum MHD_Result api_smb_add(struct MHD_Connection *conn, const char *body,
size_t size, int connecting);
enum MHD_Result api_smb_remove(struct MHD_Connection *conn, const char *body,