Showing the changes to apps/server/src/workspace/WorkspaceFileSystem.ts (1 of 2 files). Show all
Unified · Split
| @@ -24,6 +24,46 @@ import * as WorkspacePaths from "./WorkspacePaths.ts"; · 23 unmodified lines | |||
| 24 | 24 | ||
| 25 | const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024; | 25 | const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024; |
| 26 | 26 | ||
| 27 | class WorkspaceReadFileResolvedOutsideRootError extends Schema.TaggedErrorClass<WorkspaceReadFileResolvedOutsideRootError>()( | ||
| 28 | "WorkspaceReadFileResolvedOutsideRootError", | ||
| 29 | { | ||
| 30 | cwd: Schema.String, | ||
| 31 | relativePath: Schema.String, | ||
| 32 | realWorkspaceRoot: Schema.String, | ||
| 33 | realTargetPath: Schema.String, | ||
| 34 | }, | ||
| 35 | ) { | ||
| 36 | override get message(): string { | ||
| 37 | return "Workspace file path resolves outside the project root."; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | class WorkspaceReadFileNotFileError extends Schema.TaggedErrorClass<WorkspaceReadFileNotFileError>()( | ||
| 42 | "WorkspaceReadFileNotFileError", | ||
| 43 | { | ||
| 44 | cwd: Schema.String, | ||
| 45 | relativePath: Schema.String, | ||
| 46 | fileType: Schema.String, | ||
| 47 | }, | ||
| 48 | ) { | ||
| 49 | override get message(): string { | ||
| 50 | return "Workspace path is not a file."; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | class WorkspaceReadFileBinaryFileError extends Schema.TaggedErrorClass<WorkspaceReadFileBinaryFileError>()( | ||
| 55 | "WorkspaceReadFileBinaryFileError", | ||
| 56 | { | ||
| 57 | cwd: Schema.String, | ||
| 58 | relativePath: Schema.String, | ||
| 59 | nulByteOffset: Schema.Number, | ||
| 60 | }, | ||
| 61 | ) { | ||
| 62 | override get message(): string { | ||
| 63 | return "Binary files cannot be previewed as text."; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 27 | export class WorkspaceFileSystemError extends Schema.TaggedErrorClass<WorkspaceFileSystemError>()( | 67 | export class WorkspaceFileSystemError extends Schema.TaggedErrorClass<WorkspaceFileSystemError>()( |
| 28 | "WorkspaceFileSystemError", | 68 | "WorkspaceFileSystemError", |
| 29 | { | 69 | { |
| @@ -96,22 +136,40 @@ export const make = Effect.gen(function* () { · 66 unmodified lines | |||
| 96 | path.isAbsolute(relativeRealPath) | 136 | path.isAbsolute(relativeRealPath) |
| 97 | ) { | 137 | ) { |
| 98 | return yield* Effect.fail( | 138 | return yield* Effect.fail( |
| 99 | new Error("Workspace file path resolves outside the project root."), | 139 | new WorkspaceReadFileResolvedOutsideRootError({ |
| 140 | cwd: input.cwd, | ||
| 141 | relativePath: target.relativePath, | ||
| 142 | realWorkspaceRoot, | ||
| 143 | realTargetPath, | ||
| 144 | }), | ||
| 100 | ); | 145 | ); |
| 101 | } | 146 | } |
| 102 | 147 | ||
| 103 | const handle = yield* fileSystem.open(realTargetPath, { flag: "r" }); | 148 | const handle = yield* fileSystem.open(realTargetPath, { flag: "r" }); |
| 104 | const stat = yield* handle.stat; | 149 | const stat = yield* handle.stat; |
| 105 | if (stat.type !== "File") { | 150 | if (stat.type !== "File") { |
| 106 | return yield* Effect.fail(new Error("Workspace path is not a file.")); | 151 | return yield* Effect.fail( |
| 152 | new WorkspaceReadFileNotFileError({ | ||
| 153 | cwd: input.cwd, | ||
| 154 | relativePath: target.relativePath, | ||
| 155 | fileType: stat.type, | ||
| 156 | }), | ||
| 157 | ); | ||
| 107 | } | 158 | } |
| 108 | const byteLength = Number(stat.size); | 159 | const byteLength = Number(stat.size); |
| 109 | const bytesToRead = Math.min(byteLength, PROJECT_READ_FILE_MAX_BYTES); | 160 | const bytesToRead = Math.min(byteLength, PROJECT_READ_FILE_MAX_BYTES); |
| 110 | const buffer = new Uint8Array(bytesToRead); | 161 | const buffer = new Uint8Array(bytesToRead); |
| 111 | const bytesRead = yield* handle.read(buffer); | 162 | const bytesRead = yield* handle.read(buffer); |
| 112 | const fileBytes = buffer.subarray(0, Number(bytesRead)); | 163 | const fileBytes = buffer.subarray(0, Number(bytesRead)); |
| 113 | if (fileBytes.includes(0)) { | 164 | const nulByteOffset = fileBytes.indexOf(0); |
| 114 | return yield* Effect.fail(new Error("Binary files cannot be previewed as text.")); | 165 | if (nulByteOffset !== -1) { |
| 166 | return yield* Effect.fail( | ||
| 167 | new WorkspaceReadFileBinaryFileError({ | ||
| 168 | cwd: input.cwd, | ||
| 169 | relativePath: target.relativePath, | ||
| 170 | nulByteOffset, | ||
| 171 | }), | ||
| 172 | ); | ||
| 115 | } | 173 | } |
| 116 | const contents = new TextDecoder("utf-8").decode(fileBytes); | 174 | const contents = new TextDecoder("utf-8").decode(fileBytes); |
| 117 | return { | 175 | return { |