diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index b00f68514..9b0865c3d 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -30,6 +30,7 @@ import Codec, { SharedDirectoryInfoResponse, SharedDirectoryListResponse, SharedDirectoryReadResponse, + SharedDirectoryWriteResponse, FileSystemObject, } from './codec'; import { @@ -276,12 +277,23 @@ export default class Client extends EventEmitterWebAuthnSender { } } - handleSharedDirectoryWriteRequest(buffer: ArrayBuffer) { + async handleSharedDirectoryWriteRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryWriteRequest(buffer); - // TODO(isaiah): delete debug logs - this.logger.debug('Received SharedDirectoryWriteRequest:'); - this.logger.debug(req); - // TODO(isaiah): here's where we'll respond with a SharedDirectoryWriteResponse + try { + const bytesWritten = await this.sdManager.writeFile( + req.path, + req.offset, + req.writeData + ); + + this.sendSharedDirectoryWriteResponse({ + completionId: req.completionId, + errCode: SharedDirectoryErrCode.Nil, + bytesWritten, + }); + } catch (e) { + this.handleError(e); + } } async handleSharedDirectoryListRequest(buffer: ArrayBuffer) { @@ -394,6 +406,10 @@ export default class Client extends EventEmitterWebAuthnSender { this.send(this.codec.encodeSharedDirectoryReadResponse(response)); } + sendSharedDirectoryWriteResponse(response: SharedDirectoryWriteResponse) { + this.send(this.codec.encodeSharedDirectoryWriteResponse(response)); + } + resize(spec: ClientScreenSpec) { this.send(this.codec.encodeClientScreenSpec(spec)); } diff --git a/packages/teleport/src/lib/tdp/codec.ts b/packages/teleport/src/lib/tdp/codec.ts index 856c1be13..ad780aaba 100644 --- a/packages/teleport/src/lib/tdp/codec.ts +++ b/packages/teleport/src/lib/tdp/codec.ts @@ -44,6 +44,7 @@ export enum MessageType { SHARED_DIRECTORY_READ_REQUEST = 19, SHARED_DIRECTORY_READ_RESPONSE = 20, SHARED_DIRECTORY_WRITE_REQUEST = 21, + SHARED_DIRECTORY_WRITE_RESPONSE = 22, SHARED_DIRECTORY_LIST_REQUEST = 25, SHARED_DIRECTORY_LIST_RESPONSE = 26, __LAST, // utility value @@ -149,6 +150,13 @@ export type SharedDirectoryWriteRequest = { writeData: Uint8Array; }; +// | message type (22) | completion_id uint32 | err_code uint32 | bytes_written uint32 | +export type SharedDirectoryWriteResponse = { + completionId: number; + errCode: number; + bytesWritten: number; +}; + // | message type (25) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | export type SharedDirectoryListRequest = { completionId: number; @@ -570,6 +578,26 @@ export default class Codec { return buffer; } + encodeSharedDirectoryWriteResponse( + res: SharedDirectoryWriteResponse + ): Message { + const bufLen = byteLength + 3 * uint32Length; + const buffer = new ArrayBuffer(bufLen); + const view = new DataView(buffer); + let offset = 0; + + view.setUint8(offset, MessageType.SHARED_DIRECTORY_WRITE_RESPONSE); + offset += byteLength; + view.setUint32(offset, res.completionId); + offset += uint32Length; + view.setUint32(offset, res.errCode); + offset += uint32Length; + view.setUint32(offset, res.bytesWritten); + offset += uint32Length; + + return buffer; + } + // | message type (26) | completion_id uint32 | err_code uint32 | fso_list_length uint32 | fso_list fso[] | encodeSharedDirectoryListResponse(res: SharedDirectoryListResponse): Message { const bufLenSansFsoList = byteLength + 3 * uint32Length; diff --git a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts index 6c1148f72..4f2da831a 100644 --- a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts +++ b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts @@ -101,6 +101,29 @@ export class SharedDirectoryManager { ); } + // Writes the bytes in writeData to the file at path starting at offset. + async writeFile( + path: string, + offset: bigint, + writeData: Uint8Array + ): Promise { + this.checkReady(); + + const fileHandle = await this.walkPath(path); + if (fileHandle.kind !== 'file') { + throw new Error('cannot read the bytes of a directory'); + } + + const file = await fileHandle.createWritable(); + if (offset > 0) { + file.seek(Number(offset)); + } + file.write(writeData); + file.close(); // Needed to actually write data to disk. + + return writeData.length; + } + // walkPath walks a pathstr (assumed to be in the qualified Unix format specified // in the TDP spec), returning the FileSystemDirectoryHandle | FileSystemFileHandle // it finds at its end. If the pathstr isn't a valid path in the shared directory, diff --git a/yarn.lock b/yarn.lock index d2ebe8216..d8c8ad0e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10412,70 +10412,6 @@ node-gyp@8.4.1: tar "^6.1.2" which "^2.0.2" -node-gyp@8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-gyp@8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-gyp@8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-gyp@8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"