@filebox/s3-server 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,31 +1,37 @@
1
1
  {
2
2
  "name": "@filebox/s3-server",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "license": "MIT",
5
5
  "description": "S3-compatible server adapter for FileBox drivers",
6
- "main": "./index.js",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
7
10
  "exports": {
8
11
  ".": {
9
- "require": "./index.js",
10
- "default": "./index.js"
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs",
15
+ "default": "./dist/index.mjs"
11
16
  }
12
17
  },
13
18
  "files": [
14
- "index.js",
15
- "context.js",
16
- "driver.js",
17
- "responses.js",
18
- "handlers",
19
- "LICENSE",
20
- "README.md"
19
+ "dist"
21
20
  ],
22
21
  "publishConfig": {
23
22
  "access": "public"
24
23
  },
24
+ "scripts": {
25
+ "build": "node build.mjs",
26
+ "prepack": "node build.mjs"
27
+ },
25
28
  "dependencies": {
26
- "express": "^4.18.2",
27
- "xml2js": "^0.6.2",
28
- "uuid": "^9.0.1",
29
- "body-parser": "^1.20.2"
30
- }
31
- }
29
+ "express": "^4.18.2",
30
+ "xml2js": "^0.6.2",
31
+ "uuid": "^9.0.1",
32
+ "body-parser": "^1.20.2"
33
+ },
34
+ "devDependencies": {
35
+ "esbuild": "^0.24.0"
36
+ }
37
+ }
package/context.js DELETED
@@ -1,62 +0,0 @@
1
- /**
2
- * 创建S3请求上下文
3
- * @param {object} req - Express请求对象
4
- * @param {string} region - S3区域
5
- * @returns {object} 上下文对象
6
- */
7
- const createContext = (req) => {
8
- const authorization = req.headers?.authorization?.split(" ")[1];
9
- const url = new URL(req.url, `http://${req.headers.host}`);
10
- const path = url.pathname;
11
-
12
- // 解析查询参数
13
- const query = {};
14
- url.searchParams.forEach((value, key) => {
15
- query[key] = value;
16
- });
17
-
18
- const ctx = {
19
- req: req,
20
- method: (req.method || "").toLowerCase(),
21
- path: path,
22
- query: query,
23
- auth: { accessKeyId: undefined, secretAccessKey: undefined },
24
-
25
- // 获取请求头
26
- get(field) {
27
- const req = this.req;
28
- switch ((field = field.toLowerCase())) {
29
- case "referer":
30
- case "referrer":
31
- return req.headers.referrer || req.headers.referer || "";
32
- default:
33
- return req.headers[field] || "";
34
- }
35
- },
36
-
37
- // 解析S3请求参数
38
- getBucketAndKey() {
39
- // 从路径中提取bucket和key
40
- const parts = this.path.split("/").filter(Boolean);
41
- const bucket = parts[0];
42
- const key = parts.slice(1).join("/");
43
-
44
- return { bucket, key };
45
- },
46
- };
47
-
48
- // 解析认证信息
49
- if (authorization) {
50
- try {
51
- const decoded = Buffer.from(authorization, "base64").toString("utf8");
52
- const [accessKeyId, secretAccessKey] = decoded.split(":");
53
- ctx.auth = { accessKeyId, secretAccessKey };
54
- } catch (error) {
55
- // 认证解析失败
56
- }
57
- }
58
-
59
- return ctx;
60
- };
61
-
62
- module.exports = createContext;
package/driver.js DELETED
@@ -1,337 +0,0 @@
1
- const path = require("path");
2
- const fs = require("fs").promises;
3
- const { createReadStream, createWriteStream } = require("fs");
4
- const { Readable } = require("stream");
5
- const crypto = require("crypto");
6
-
7
- /**
8
- * S3驱动程序接口
9
- * 定义了S3服务器所需的所有存储操作
10
- */
11
- class S3Driver {
12
- /**
13
- * 创建S3驱动程序实例
14
- * @param {Object} config - 驱动配置
15
- */
16
- constructor(config = {}) {
17
- this.config = {
18
- rootDir: config.rootDir || "./s3-data",
19
- ...config,
20
- };
21
- }
22
-
23
- /**
24
- * 确保目录存在
25
- * @param {string} dirPath - 目录路径
26
- * @returns {Promise<void>}
27
- */
28
- async ensureDir(dirPath) {
29
- try {
30
- await fs.mkdir(dirPath, { recursive: true });
31
- } catch (error) {
32
- if (error.code !== "EEXIST") {
33
- throw error;
34
- }
35
- }
36
- }
37
-
38
- /**
39
- * 获取存储桶路径
40
- * @param {string} bucket - 存储桶名称
41
- * @returns {string} 存储桶完整路径
42
- */
43
- getBucketPath(bucket) {
44
- return path.join(this.config.rootDir, bucket);
45
- }
46
-
47
- /**
48
- * 获取对象路径
49
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
50
- * @returns {string} 对象完整路径
51
- */
52
- getObjectPath(objectPath) {
53
- // 移除开头的斜杠
54
- const normalizedPath = objectPath.startsWith("/")
55
- ? objectPath.slice(1)
56
- : objectPath;
57
- return path.join(this.config.rootDir, normalizedPath);
58
- }
59
-
60
- /**
61
- * 检查存储桶是否存在
62
- * @param {string} bucket - 存储桶名称
63
- * @returns {Promise<boolean>} 存储桶是否存在
64
- */
65
- async bucketExists(bucket) {
66
- try {
67
- const bucketPath = this.getBucketPath(bucket);
68
- const stats = await fs.stat(bucketPath);
69
- return stats.isDirectory();
70
- } catch (error) {
71
- return false;
72
- }
73
- }
74
-
75
- /**
76
- * 检查是否是存储桶所有者
77
- * @param {string} bucket - 存储桶名称
78
- * @returns {Promise<boolean>} 是否是存储桶所有者
79
- */
80
- async isBucketOwner(bucket) {
81
- // 在此实现中,假设所有存在的存储桶都属于当前用户
82
- return await this.bucketExists(bucket);
83
- }
84
-
85
- /**
86
- * 检查是否有权限访问存储桶
87
- * @param {string} bucket - 存储桶名称
88
- * @returns {Promise<boolean>} 是否有权限访问
89
- */
90
- async hasBucketAccess(bucket) {
91
- // 在此实现中,假设所有存在的存储桶都可以访问
92
- return await this.bucketExists(bucket);
93
- }
94
-
95
- /**
96
- * 检查存储桶是否为空
97
- * @param {string} bucket - 存储桶名称
98
- * @returns {Promise<boolean>} 存储桶是否为空
99
- */
100
- async isBucketEmpty(bucket) {
101
- try {
102
- const bucketPath = this.getBucketPath(bucket);
103
- const files = await fs.readdir(bucketPath);
104
- return files.length === 0;
105
- } catch (error) {
106
- // 如果存储桶不存在,视为空
107
- return true;
108
- }
109
- }
110
-
111
- /**
112
- * 创建存储桶
113
- * @param {string} bucket - 存储桶名称
114
- * @returns {Promise<void>}
115
- */
116
- async createBucket(bucket) {
117
- const bucketPath = this.getBucketPath(bucket);
118
- await this.ensureDir(bucketPath);
119
-
120
- // 创建元数据文件
121
- const metadataPath = path.join(bucketPath, ".metadata.json");
122
- const metadata = {
123
- creationDate: Date.now(),
124
- owner: this.config.owner || "default",
125
- };
126
-
127
- await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
128
- }
129
-
130
- /**
131
- * 删除存储桶
132
- * @param {string} bucket - 存储桶名称
133
- * @returns {Promise<void>}
134
- */
135
- async deleteBucket(bucket) {
136
- const bucketPath = this.getBucketPath(bucket);
137
- await fs.rmdir(bucketPath, { recursive: true });
138
- }
139
-
140
- /**
141
- * 列出所有存储桶
142
- * @returns {Promise<Array>} 存储桶列表
143
- */
144
- async listBuckets() {
145
- try {
146
- // 确保根目录存在
147
- await this.ensureDir(this.config.rootDir);
148
-
149
- // 读取根目录下的所有目录
150
- const entries = await fs.readdir(this.config.rootDir, {
151
- withFileTypes: true,
152
- });
153
- const buckets = [];
154
-
155
- for (const entry of entries) {
156
- if (entry.isDirectory()) {
157
- try {
158
- // 尝试读取存储桶元数据
159
- const metadataPath = path.join(
160
- this.config.rootDir,
161
- entry.name,
162
- ".metadata.json",
163
- );
164
- let metadata;
165
-
166
- try {
167
- const data = await fs.readFile(metadataPath, "utf8");
168
- metadata = JSON.parse(data);
169
- } catch (error) {
170
- // 如果元数据文件不存在或无效,使用默认值
171
- metadata = {
172
- creationDate: Date.now(),
173
- owner: "default",
174
- };
175
- }
176
-
177
- buckets.push({
178
- name: entry.name,
179
- creationDate: metadata.creationDate,
180
- });
181
- } catch (error) {
182
- console.error(`读取存储桶 ${entry.name} 的元数据时出错:`, error);
183
- }
184
- }
185
- }
186
-
187
- return buckets;
188
- } catch (error) {
189
- console.error("列出存储桶时出错:", error);
190
- return [];
191
- }
192
- }
193
-
194
- /**
195
- * 获取对象信息
196
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
197
- * @returns {Promise<Object|null>} 对象信息或null(如果不存在)
198
- */
199
- async stat(objectPath) {
200
- try {
201
- const fullPath = this.getObjectPath(objectPath);
202
- const stats = await fs.stat(fullPath);
203
-
204
- return {
205
- size: stats.size,
206
- mtime: stats.mtime.getTime(),
207
- isDirectory: stats.isDirectory(),
208
- uuid: crypto.createHash("md5").update(fullPath).digest("hex"),
209
- };
210
- } catch (error) {
211
- return null;
212
- }
213
- }
214
-
215
- /**
216
- * 读取目录内容
217
- * @param {string} dirPath - 目录路径(格式:/bucket/key)
218
- * @returns {Promise<Array>} 目录内容列表
219
- */
220
- async readdir(dirPath) {
221
- try {
222
- const fullPath = this.getObjectPath(dirPath);
223
- const entries = await fs.readdir(fullPath);
224
-
225
- // 过滤掉元数据文件
226
- return entries.filter((entry) => !entry.startsWith("."));
227
- } catch (error) {
228
- return [];
229
- }
230
- }
231
-
232
- /**
233
- * 创建读取流
234
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
235
- * @param {Object} options - 选项
236
- * @returns {Promise<ReadableStream>} 可读流
237
- */
238
- async createReadStream(objectPath, options = {}) {
239
- const fullPath = this.getObjectPath(objectPath);
240
-
241
- try {
242
- // 检查文件是否存在
243
- await fs.access(fullPath);
244
-
245
- // 创建读取流
246
- return createReadStream(fullPath, options);
247
- } catch (error) {
248
- // 如果文件不存在,返回空流
249
- const emptyStream = new Readable();
250
- emptyStream.push(null);
251
- return emptyStream;
252
- }
253
- }
254
-
255
- /**
256
- * 创建写入流
257
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
258
- * @returns {Promise<WritableStream>} 可写流
259
- */
260
- async createWriteStream(objectPath) {
261
- const fullPath = this.getObjectPath(objectPath);
262
-
263
- // 确保父目录存在
264
- const dirPath = path.dirname(fullPath);
265
- await this.ensureDir(dirPath);
266
-
267
- // 创建写入流
268
- return createWriteStream(fullPath);
269
- }
270
-
271
- /**
272
- * 删除对象
273
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
274
- * @returns {Promise<void>}
275
- */
276
- async unlink(objectPath) {
277
- const fullPath = this.getObjectPath(objectPath);
278
- await fs.unlink(fullPath);
279
- }
280
-
281
- /**
282
- * 更新对象元数据
283
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
284
- * @param {Object} metadata - 元数据
285
- * @returns {Promise<void>}
286
- */
287
- async updateObjectMetadata(objectPath, metadata) {
288
- const fullPath = this.getObjectPath(objectPath);
289
- const metadataPath = `${fullPath}.metadata.json`;
290
-
291
- try {
292
- // 读取现有元数据(如果存在)
293
- let existingMetadata = {};
294
-
295
- try {
296
- const data = await fs.readFile(metadataPath, "utf8");
297
- existingMetadata = JSON.parse(data);
298
- } catch (error) {
299
- // 如果元数据文件不存在或无效,使用空对象
300
- }
301
-
302
- // 合并元数据
303
- const updatedMetadata = {
304
- ...existingMetadata,
305
- ...metadata,
306
- lastModified: new Date().toISOString(),
307
- };
308
-
309
- // 写入元数据文件
310
- await fs.writeFile(
311
- metadataPath,
312
- JSON.stringify(updatedMetadata, null, 2),
313
- );
314
- } catch (error) {
315
- console.error("更新对象元数据时出错:", error);
316
- }
317
- }
318
-
319
- /**
320
- * 获取对象元数据
321
- * @param {string} objectPath - 对象路径(格式:/bucket/key)
322
- * @returns {Promise<Object|null>} 元数据或null(如果不存在)
323
- */
324
- async getObjectMetadata(objectPath) {
325
- const fullPath = this.getObjectPath(objectPath);
326
- const metadataPath = `${fullPath}.metadata.json`;
327
-
328
- try {
329
- const data = await fs.readFile(metadataPath, "utf8");
330
- return JSON.parse(data);
331
- } catch (error) {
332
- return null;
333
- }
334
- }
335
- }
336
-
337
- module.exports = S3Driver;
@@ -1,129 +0,0 @@
1
- const Responses = require("../responses");
2
-
3
- /**
4
- * 处理S3 CreateBucket请求
5
- * 创建新的存储桶
6
- */
7
- class CreateBucket {
8
- constructor(server) {
9
- this.server = server;
10
- this.handle = this.handle.bind(this);
11
- }
12
-
13
- /**
14
- * 处理CreateBucket请求
15
- * @param {object} req - Express请求对象
16
- * @param {object} res - Express响应对象
17
- */
18
- async handle(req, res) {
19
- try {
20
- // 获取存储桶名称
21
- const { bucket } = this.extractBucketAndKey(req);
22
-
23
- if (!bucket) {
24
- await Responses.error(
25
- res,
26
- 400,
27
- "InvalidBucketName",
28
- "无效的存储桶名称",
29
- );
30
- return;
31
- }
32
-
33
- // 验证存储桶名称
34
- if (!this.isValidBucketName(bucket)) {
35
- await Responses.error(
36
- res,
37
- 400,
38
- "InvalidBucketName",
39
- "存储桶名称不符合规范",
40
- );
41
- return;
42
- }
43
-
44
- // 检查存储桶是否已存在
45
- const bucketExists = await this.server.driver.bucketExists(bucket);
46
-
47
- if (bucketExists) {
48
- // 如果存储桶已存在且属于当前用户,返回200
49
- if (await this.server.driver.isBucketOwner(bucket)) {
50
- res.set("Location", `/${bucket}`);
51
- await Responses.ok(res);
52
- return;
53
- }
54
-
55
- // 如果存储桶已存在但不属于当前用户,返回409
56
- await Responses.error(
57
- res,
58
- 409,
59
- "BucketAlreadyExists",
60
- "存储桶已存在且不属于您",
61
- );
62
- return;
63
- }
64
-
65
- // 创建存储桶
66
- await this.server.driver.createBucket(bucket);
67
-
68
- // 设置Location头
69
- res.set("Location", `/${bucket}`);
70
-
71
- // 返回成功响应
72
- await Responses.ok(res);
73
- } catch (error) {
74
- console.error("Error in CreateBucket:", error);
75
- await Responses.error(res, 500, "InternalError", "服务器内部错误");
76
- }
77
- }
78
-
79
- /**
80
- * 从请求参数中提取存储桶和键
81
- * @param {object} req - Express请求对象
82
- * @returns {object} 包含存储桶和键的对象
83
- */
84
- extractBucketAndKey(req) {
85
- const url = new URL(req.url, `http://${req.headers.host}`);
86
- const pathParts = url.pathname.split("/").filter(Boolean);
87
-
88
- return {
89
- bucket: pathParts[0],
90
- key: pathParts.slice(1).join("/"),
91
- };
92
- }
93
-
94
- /**
95
- * 验证存储桶名称是否符合规范
96
- * @param {string} name - 存储桶名称
97
- * @returns {boolean} 是否有效
98
- */
99
- isValidBucketName(name) {
100
- // 存储桶命名规则:
101
- // - 3到63个字符
102
- // - 只能包含小写字母、数字和连字符
103
- // - 必须以字母或数字开头和结尾
104
- // - 不能是IP地址格式
105
-
106
- if (!name || name.length < 3 || name.length > 63) {
107
- return false;
108
- }
109
-
110
- // 检查是否只包含有效字符
111
- if (!/^[a-z0-9][a-z0-9.-]*[a-z0-9]$/.test(name)) {
112
- return false;
113
- }
114
-
115
- // 检查是否是IP地址格式
116
- if (/^\d+\.\d+\.\d+\.\d+$/.test(name)) {
117
- return false;
118
- }
119
-
120
- // 检查是否包含连续的点或连字符
121
- if (name.includes("..") || name.includes(".-") || name.includes("-.")) {
122
- return false;
123
- }
124
-
125
- return true;
126
- }
127
- }
128
-
129
- module.exports = CreateBucket;
@@ -1,84 +0,0 @@
1
- const Responses = require("../responses");
2
-
3
- /**
4
- * 处理S3 DeleteBucket请求
5
- * 删除存储桶
6
- */
7
- class DeleteBucket {
8
- constructor(server) {
9
- this.server = server;
10
- this.handle = this.handle.bind(this);
11
- }
12
-
13
- /**
14
- * 处理DeleteBucket请求
15
- * @param {object} req - Express请求对象
16
- * @param {object} res - Express响应对象
17
- */
18
- async handle(req, res) {
19
- try {
20
- // 获取存储桶名称
21
- const { bucket } = this.extractBucketAndKey(req);
22
-
23
- if (!bucket) {
24
- await Responses.error(
25
- res,
26
- 400,
27
- "InvalidBucketName",
28
- "无效的存储桶名称",
29
- );
30
- return;
31
- }
32
-
33
- // 检查存储桶是否存在
34
- const bucketExists = await this.server.driver.bucketExists(bucket);
35
-
36
- if (!bucketExists) {
37
- await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
38
- return;
39
- }
40
-
41
- // 检查存储桶是否为空
42
- const isEmpty = await this.server.driver.isBucketEmpty(bucket);
43
-
44
- if (!isEmpty) {
45
- await Responses.error(res, 409, "BucketNotEmpty", "存储桶不为空");
46
- return;
47
- }
48
-
49
- // 检查是否有权限删除存储桶
50
- const isOwner = await this.server.driver.isBucketOwner(bucket);
51
-
52
- if (!isOwner) {
53
- await Responses.error(res, 403, "AccessDenied", "没有权限删除此存储桶");
54
- return;
55
- }
56
-
57
- // 删除存储桶
58
- await this.server.driver.deleteBucket(bucket);
59
-
60
- // 返回成功响应
61
- await Responses.noContent(res);
62
- } catch (error) {
63
- console.error("Error in DeleteBucket:", error);
64
- await Responses.error(res, 500, "InternalError", "服务器内部错误");
65
- }
66
- }
67
-
68
- /**
69
- * 从请求参数中提取存储桶和键
70
- * @param {object} req - Express请求对象
71
- * @returns {object} 包含存储桶和键的对象
72
- */
73
- extractBucketAndKey(req) {
74
- const url = new URL(req.url, `http://${req.headers.host}`);
75
- const pathParts = url.pathname.split("/").filter(Boolean);
76
-
77
- return {
78
- bucket: pathParts[0],
79
- key: pathParts.slice(1).join("/"),
80
- };
81
- }
82
- }
83
-
84
- module.exports = DeleteBucket;