Skip to content

Commit 1ab489a

Browse files
committed
feat: Add symlinks support in SFTP(#1076)
1 parent a9e4cd9 commit 1ab489a

1 file changed

Lines changed: 74 additions & 10 deletions

File tree

src/fileSystem/sftp.js

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,19 @@ class SftpClient {
7878
if (stat) options += "d";
7979

8080
sftp.exec(
81-
`ls ${options} --full-time "${path}" | awk '{$2=\"\"; print $0}'`,
82-
(res) => {
81+
`ls ${options} --full-time -L "${path}" | awk '{$2=\"\"; print $0}'`,
82+
async (res) => {
8383
if (res.code <= 0) {
8484
if (stat) {
85-
resolve(this.#parseFile(res.result, Url.dirname(filename)));
85+
const file = await this.#parseFile(
86+
res.result,
87+
Url.dirname(filename),
88+
);
89+
resolve(file);
8690
return;
8791
}
88-
resolve(this.#parseDir(filename, res.result));
92+
const dirList = await this.#parseDir(filename, res.result);
93+
resolve(dirList);
8994
return;
9095
}
9196
reject(this.#errorCodes(res.code));
@@ -490,18 +495,19 @@ class SftpClient {
490495
* @param {String} dirname
491496
* @param {String} res
492497
*/
493-
#parseDir(dirname, res) {
498+
async #parseDir(dirname, res) {
494499
if (!res) return [];
495500

496501
const list = res.split("\n");
497502

498503
if (/total/.test(list[0])) list.splice(0, 1);
499504

500-
const fileList = list.map((i) => this.#parseFile(i, dirname));
505+
const filePromises = list.map((i) => this.#parseFile(i, dirname));
506+
const fileList = await Promise.all(filePromises);
501507
return fileList.filter((i) => !!i);
502508
}
503509

504-
#parseFile(item, dirname) {
510+
async #parseFile(item, dirname) {
505511
if (!item) return null;
506512
const PERMISSIONS = 0;
507513
const SIZE = 2;
@@ -536,9 +542,67 @@ class SftpClient {
536542
const canrw = permissions.substr(1, 2);
537543
const type = DIR_TYPE(permissions[0]);
538544

545+
let isDirectory = type === "directory";
546+
let isFile = type === "file";
547+
let targetType = type;
548+
539549
if (type === "link") {
540550
name.splice(name.indexOf("->"));
551+
if (name.length === 0) return null;
552+
const symlinkPath = Url.join(
553+
dirname,
554+
Url.basename(name[name.length - 1]),
555+
);
556+
let linkTarget = await new Promise((resolve, reject) => {
557+
sftp.exec(
558+
`readlink "${this.#safeName(symlinkPath)}"`,
559+
(res) => {
560+
if (res.code <= 0) {
561+
resolve(res.result.trim());
562+
} else {
563+
reject(this.#errorCodes(res.code));
564+
}
565+
},
566+
(err) => {
567+
reject(err);
568+
},
569+
);
570+
});
571+
linkTarget = linkTarget.startsWith("/")
572+
? linkTarget
573+
: Url.join(dirname, linkTarget);
574+
575+
targetType = await new Promise((resolve, reject) => {
576+
sftp.exec(
577+
`ls -ld "${this.#safeName(linkTarget)}"`,
578+
(res) => {
579+
if (res.code <= 0) {
580+
const output = res.result.trim();
581+
switch (output[0]) {
582+
case "d":
583+
resolve("directory");
584+
break;
585+
default:
586+
resolve("file");
587+
}
588+
} else {
589+
resolve(["file", this.#errorCodes(res.code)]);
590+
}
591+
},
592+
(err) => {
593+
// If the exec fails, maybe the target is deleted, continue anyway
594+
resolve(["file", this.#errorCodes(res.code)]);
595+
},
596+
);
597+
});
598+
if (Array.isArray(targetType)) {
599+
const [type, err] = targetType;
600+
targetType = type;
601+
}
602+
isDirectory = targetType === "directory";
603+
isFile = targetType === "file";
541604
}
605+
542606
name = Url.basename(name.join(" "));
543607
if (["..", ".", "`"].includes(name)) return null;
544608

@@ -550,13 +614,13 @@ class SftpClient {
550614
url,
551615
name,
552616
size,
553-
type,
617+
type: targetType,
554618
uri: url,
555619
canRead: /r/.test(canrw),
556620
canWrite: /w/.test(canrw),
557-
isDirectory: type === "directory",
621+
isDirectory,
558622
isLink: type === "link",
559-
isFile: type === "file",
623+
isFile,
560624
modifiedDate: date,
561625
};
562626
}

0 commit comments

Comments
 (0)