Skip to content

Commit

Permalink
Merge pull request #42 from daleswift/feature/remove-rename
Browse files Browse the repository at this point in the history
Feature/remove rename
  • Loading branch information
Joannis authored Sep 2, 2023
2 parents 3e920e6 + 2e661da commit 2192631
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 9 deletions.
47 changes: 47 additions & 0 deletions Sources/Citadel/SFTP/Client/SFTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,53 @@ public final class SFTPClient {

self.logger.debug("SFTP created directory \(path)")
}

/// Remove a file at the specified path on the SFTP server
public func remove(
at filePath: String
) async throws {
self.logger.info("SFTP requesting remove file at '\(filePath)'")

let _ = try await sendRequest(.remove(.init(
requestId: allocateRequestId(),
filename: filePath
)))

self.logger.debug("SFTP removed file at \(filePath)")
}

/// Remove a directory at the specified path on the SFTP server
public func rmdir(
at filePath: String
) async throws {
self.logger.info("SFTP requesting remove directory at '\(filePath)'")

let _ = try await sendRequest(.rmdir(.init(
requestId: allocateRequestId(),
filePath: filePath
)))

self.logger.debug("SFTP removed directory at \(filePath)")
}

/// Rename a file
public func rename(
at oldPath: String,
to newPath: String,
flags: UInt32 = 0
) async throws {
self.logger.info("SFTP requesting rename file at '\(oldPath)' to '\(newPath)'")

let _ = try await sendRequest(.rename(.init(
requestId: allocateRequestId(),
oldPath: oldPath,
newPath: newPath,
flags: flags
)))

self.logger.debug("SFTP renamed file at \(oldPath) to \(newPath)")
}

}

extension SSHClient {
Expand Down
4 changes: 3 additions & 1 deletion Sources/Citadel/SFTP/SFTPBasicEnums.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public enum SFTPMessageType: UInt8 {
case rmdir = 15
case realpath = 16
case stat = 17
case readlink = 18
case rename = 18
case readlink = 19
case symlink = 20

case status = 101
Expand Down Expand Up @@ -84,6 +85,7 @@ public enum SFTPMessageType: UInt8 {
case .rmdir: return "SSH_FXP_RMDIR"
case .realpath: return "SSH_FXP_REALPATH"
case .stat: return "SSH_FXP_STAT"
case .rename: return "SSH_FXP_RENAME"
case .readlink: return "SSH_FXP_READLINK"
case .symlink: return "SSH_FXP_SYMLINK"

Expand Down
46 changes: 40 additions & 6 deletions Sources/Citadel/SFTP/SFTPMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ enum SFTPRequest: CustomDebugStringConvertible {
case readdir(SFTPMessage.ReadDir)
case opendir(SFTPMessage.OpenDir)
case realpath(SFTPMessage.RealPath)

case remove(SFTPMessage.Remove)
case rmdir(SFTPMessage.RmDir)
case rename(SFTPMessage.Rename)

var requestId: UInt32 {
get {
switch self {
Expand All @@ -56,6 +59,12 @@ enum SFTPRequest: CustomDebugStringConvertible {
return message.requestId
case .realpath(let message):
return message.requestId
case .remove(let message):
return message.requestId
case .rmdir(let message):
return message.requestId
case .rename(let message):
return message.requestId
}
}
}
Expand All @@ -82,6 +91,12 @@ enum SFTPRequest: CustomDebugStringConvertible {
return .readdir(message)
case .realpath(let message):
return .realpath(message)
case .remove(let message):
return .remove(message)
case .rmdir(let message):
return .rmdir(message)
case .rename(let message):
return .rename(message)
}
}

Expand All @@ -97,6 +112,9 @@ enum SFTPRequest: CustomDebugStringConvertible {
case .readdir(let message): return message.debugDescription
case .opendir(let message): return message.debugDescription
case .realpath(let message): return message.debugDescription
case .remove(let message): return message.debugDescription
case .rmdir(let message): return message.debugDescription
case .rename(let message): return message.debugDescription
}
}
}
Expand Down Expand Up @@ -159,7 +177,7 @@ enum SFTPResponse {
self = .name(message)
case .attributes(let message):
self = .attributes(message)
case .realpath, .openFile, .fstat, .closeFile, .read, .write, .initialize, .version, .stat, .lstat, .rmdir, .opendir, .readdir, .remove, .fsetstat, .setstat, .symlink, .readlink:
case .realpath, .openFile, .fstat, .closeFile, .read, .write, .initialize, .version, .stat, .lstat, .rmdir, .opendir, .readdir, .remove, .fsetstat, .setstat, .symlink, .readlink, .rename:
return nil
}
}
Expand Down Expand Up @@ -318,7 +336,19 @@ public enum SFTPMessage {
public var debugDescription: String { "{\(self.requestId)}(\(self.path),\(self.attributes)" }
fileprivate var debugVariantWithoutLargeData: Self { self }
}


public struct Rename: SFTPMessageContent {
public static let id = SFTPMessageType.rename

public let requestId: UInt32
public var oldPath: String
public var newPath: String
public var flags: UInt32

public var debugDescription: String { "{\(self.requestId)}(\(self.oldPath),\(self.newPath),\(self.flags))" }
fileprivate var debugVariantWithoutLargeData: Self { self }
}

public struct Symlink: SFTPMessageContent {
public static let id = SFTPMessageType.symlink

Expand All @@ -331,7 +361,7 @@ public enum SFTPMessage {
}

public struct Readlink: SFTPMessageContent {
public static let id = SFTPMessageType.symlink
public static let id = SFTPMessageType.readlink

public let requestId: UInt32
public var path: String
Expand Down Expand Up @@ -524,6 +554,7 @@ public enum SFTPMessage {
case name(Name)
case attributes(Attributes)
case readdir(ReadDir)
case rename(Rename)

public var messageType: SFTPMessageType {
switch self {
Expand Down Expand Up @@ -551,7 +582,8 @@ public enum SFTPMessage {
.fsetstat(let message as SFTPMessageContent),
.setstat(let message as SFTPMessageContent),
.symlink(let message as SFTPMessageContent),
.readlink(let message as SFTPMessageContent):
.readlink(let message as SFTPMessageContent),
.rename(let message as SFTPMessageContent):
return message.id
}
}
Expand Down Expand Up @@ -582,7 +614,8 @@ public enum SFTPMessage {
.fsetstat(let message as SFTPMessageContent),
.setstat(let message as SFTPMessageContent),
.symlink(let message as SFTPMessageContent),
.readlink(let message as SFTPMessageContent):
.readlink(let message as SFTPMessageContent),
.rename(let message as SFTPMessageContent):
return "\(message.id)\(message.debugDescription)"
}
}
Expand Down Expand Up @@ -613,6 +646,7 @@ public enum SFTPMessage {
case .setstat(let message): return Self.setstat(message.debugVariantWithoutLargeData)
case .symlink(let message): return Self.symlink(message.debugVariantWithoutLargeData)
case .readlink(let message): return Self.readlink(message.debugVariantWithoutLargeData)
case .rename(let message): return Self.rename(message.debugVariantWithoutLargeData)
}
}

Expand Down
19 changes: 19 additions & 0 deletions Sources/Citadel/SFTP/SFTPMessageParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,25 @@ struct SFTPMessageParser: ByteToMessageDecoder {
attributes: attributes
)
)
case .rename:
guard
let requestId = payload.readInteger(as: UInt32.self),
let oldPath = payload.readSSHString(),
let newPath = payload.readSSHString(),
let flags = payload.readInteger(as: UInt32.self)
else {
throw SFTPError.invalidPayload(type: type)
}

message = .rename(
.init(
requestId: requestId,
oldPath: oldPath,
newPath: newPath,
flags: flags
)
)

case .readlink:
guard
let requestId = payload.readInteger(as: UInt32.self),
Expand Down
11 changes: 11 additions & 0 deletions Sources/Citadel/SFTP/SFTPSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,33 @@ final class SFTPMessageSerializer: MessageToByteEncoder {
out.writeSSHString(&fstat.handle)
case .remove(let remove):
out.writeInteger(SFTPMessage.Remove.id.rawValue)
out.writeInteger(remove.requestId)
out.writeSSHString(remove.filename)
case .fsetstat(var fsetstat):
out.writeInteger(SFTPMessage.FileSetStat.id.rawValue)
out.writeInteger(fsetstat.requestId)
out.writeSSHString(&fsetstat.handle)
out.writeSFTPFileAttributes(fsetstat.attributes)
case .setstat(let setstat):
out.writeInteger(SFTPMessage.SetStat.id.rawValue)
out.writeInteger(setstat.requestId)
out.writeSSHString(setstat.path)
out.writeSFTPFileAttributes(setstat.attributes)
case .symlink(let symlink):
out.writeInteger(SFTPMessage.Symlink.id.rawValue)
out.writeInteger(symlink.requestId)
out.writeSSHString(symlink.linkPath)
out.writeSSHString(symlink.targetPath)
case .readlink(let readlink):
out.writeInteger(SFTPMessage.Symlink.id.rawValue)
out.writeInteger(readlink.requestId)
out.writeSSHString(readlink.path)
case .rename(let rename):
out.writeInteger(SFTPMessage.Rename.id.rawValue)
out.writeInteger(rename.requestId)
out.writeSSHString(rename.oldPath)
out.writeSSHString(rename.newPath)
out.writeInteger(rename.flags)
}

let length = out.writerIndex - lengthIndex - 4
Expand Down
3 changes: 3 additions & 0 deletions Sources/Citadel/SFTP/Server/SFTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public protocol SFTPDelegate {

/// Reads the target of the symbolic link at the given path. This is equivalent to the `readlink()` system call.
func readSymlink(atPath path: String, context: SSHContext) async throws -> [SFTPPathComponent]

/// Renames a file
func rename(oldPath: String, newPath: String, flags: UInt32, context: SSHContext) async throws -> SFTPStatusCode
}

struct SFTPServerSubsystem {
Expand Down
28 changes: 27 additions & 1 deletion Sources/Citadel/SFTP/Server/SFTPServerInboundHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,31 @@ final class SFTPServerInboundHandler: ChannelInboundHandler {
)
}.flatMapErrorThrowing { _ in }
}


func rename(command: SFTPMessage.Rename, context:ChannelHandlerContext) {
let promise = context.eventLoop.makePromise(of: SFTPStatusCode.self)
promise.completeWithTask {
try await self.delegate.rename(
oldPath: command.oldPath,
newPath: command.newPath,
flags: command.flags,
context: self.makeContext()
)
}
_ = promise.futureResult.flatMap { status -> EventLoopFuture<Void> in
context.channel.writeAndFlush(
SFTPMessage.status(
SFTPMessage.Status(
requestId: command.requestId,
errorCode: status,
message: "",
languageTag: "EN"
)
)
)
}.flatMapErrorThrowing { _ in }
}

func readlink(command: SFTPMessage.Readlink, context:ChannelHandlerContext) {
let promise = context.eventLoop.makePromise(of: [SFTPPathComponent].self)
promise.completeWithTask {
Expand Down Expand Up @@ -554,6 +578,8 @@ final class SFTPServerInboundHandler: ChannelInboundHandler {
symlink(command: command, context: context)
case .readlink(let command):
readlink(command: command, context: context)
case .rename(let command):
rename(command: command, context: context)
case .version, .handle, .status, .data, .attributes, .name:
// Client cannot send these messages
context.channel.triggerUserOutboundEvent(ChannelFailureEvent()).whenComplete { _ in
Expand Down
6 changes: 5 additions & 1 deletion Tests/CitadelTests/Citadel2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ final class Citadel2Tests: XCTestCase {
func addSymlink(linkPath: String, targetPath: String, context: Citadel.SSHContext) async throws -> Citadel.SFTPStatusCode {
throw DelegateError.unsupported
}


func rename(oldPath: String, newPath: String, flags: UInt32, context: Citadel.SSHContext) async throws -> Citadel.SFTPStatusCode {
throw DelegateError.unsupported
}

func readSymlink(atPath path: String, context: Citadel.SSHContext) async throws -> [Citadel.SFTPPathComponent] {
throw DelegateError.unsupported
}
Expand Down

0 comments on commit 2192631

Please sign in to comment.