@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/dist/index.cjs ADDED
@@ -0,0 +1,1372 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.ts
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ default: () => S3Server
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+ var import_body_parser = __toESM(require("body-parser"), 1);
36
+ var import_express = __toESM(require("express"), 1);
37
+ var import_http = __toESM(require("http"), 1);
38
+ var import_https = __toESM(require("https"), 1);
39
+
40
+ // src/context.ts
41
+ var createContext = (req) => {
42
+ const authorization = req.headers?.authorization?.split(" ")[1];
43
+ const url = new URL(req.url, `http://${req.headers.host}`);
44
+ const path4 = url.pathname;
45
+ const query = {};
46
+ url.searchParams.forEach((value, key) => {
47
+ query[key] = value;
48
+ });
49
+ const ctx = {
50
+ req,
51
+ method: (req.method || "").toLowerCase(),
52
+ path: path4,
53
+ query,
54
+ auth: { accessKeyId: void 0, secretAccessKey: void 0 },
55
+ // 获取请求头
56
+ get(field) {
57
+ const req2 = this.req;
58
+ switch (field = field.toLowerCase()) {
59
+ case "referer":
60
+ case "referrer":
61
+ return req2.headers.referrer || req2.headers.referer || "";
62
+ default:
63
+ return req2.headers[field] || "";
64
+ }
65
+ },
66
+ // 解析S3请求参数
67
+ getBucketAndKey() {
68
+ const parts = this.path.split("/").filter(Boolean);
69
+ const bucket = parts[0];
70
+ const key = parts.slice(1).join("/");
71
+ return { bucket, key };
72
+ }
73
+ };
74
+ if (authorization) {
75
+ try {
76
+ const decoded = Buffer.from(authorization, "base64").toString("utf8");
77
+ const [accessKeyId, secretAccessKey] = decoded.split(":");
78
+ ctx.auth = { accessKeyId, secretAccessKey };
79
+ } catch (error) {
80
+ }
81
+ }
82
+ return ctx;
83
+ };
84
+ var context_default = createContext;
85
+
86
+ // src/responses.ts
87
+ var import_crypto = __toESM(require("crypto"), 1);
88
+ var import_xml2js = require("xml2js");
89
+ var Responses = class {
90
+ constructor() {
91
+ this.xmlBuilder = new import_xml2js.Builder({
92
+ xmldec: {
93
+ version: "1.0",
94
+ encoding: "utf-8"
95
+ }
96
+ });
97
+ }
98
+ /**
99
+ * 发送XML响应
100
+ * @param {object} res - Express响应对象
101
+ * @param {object} xmlObject - 要转换为XML的对象
102
+ * @param {number} status - HTTP状态码
103
+ */
104
+ async sendXmlResponse(res, xmlObject, status = 200) {
105
+ if (res.headersSent) {
106
+ return;
107
+ }
108
+ const response = this.xmlBuilder.buildObject(xmlObject);
109
+ res.set("Content-Type", "application/xml; charset=utf-8");
110
+ res.set("Content-Length", Buffer.byteLength(response).toString());
111
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
112
+ res.status(status);
113
+ return new Promise((resolve) => {
114
+ res.end(response, () => {
115
+ resolve();
116
+ });
117
+ });
118
+ }
119
+ /**
120
+ * 发送错误响应
121
+ * @param {object} res - Express响应对象
122
+ * @param {number} status - HTTP状态码
123
+ * @param {string} code - 错误代码
124
+ * @param {string} message - 错误消息
125
+ */
126
+ async error(res, status, code, message) {
127
+ return this.sendXmlResponse(
128
+ res,
129
+ {
130
+ Error: {
131
+ Code: code,
132
+ Message: message
133
+ }
134
+ },
135
+ status
136
+ );
137
+ }
138
+ /**
139
+ * 列出所有存储桶
140
+ * @param {object} res - Express响应对象
141
+ * @param {Array} buckets - 存储桶列表
142
+ * @param {object} owner - 所有者信息
143
+ */
144
+ async listBuckets(res, buckets, owner) {
145
+ return this.sendXmlResponse(res, {
146
+ ListAllMyBucketsResult: {
147
+ $: { xmlns: "http://s3.amazonaws.com/doc/2006-03-01/" },
148
+ Buckets: {
149
+ Bucket: buckets.map((bucket) => ({
150
+ CreationDate: new Date(bucket.creationDate).toISOString(),
151
+ Name: bucket.name
152
+ }))
153
+ },
154
+ Owner: {
155
+ ID: import_crypto.default.createHash("sha256").update(owner.id).digest("hex"),
156
+ DisplayName: owner.displayName || ""
157
+ }
158
+ }
159
+ });
160
+ }
161
+ /**
162
+ * 列出对象(V2)
163
+ * @param {object} res - Express响应对象
164
+ * @param {string} prefix - 前缀
165
+ * @param {Array} objects - 对象列表
166
+ * @param {Array} commonPrefixes - 公共前缀
167
+ * @param {string} bucket - 存储桶名称
168
+ * @param {string} delimiter - 分隔符
169
+ */
170
+ async listObjectsV2(res, prefix, objects, commonPrefixes, bucket, delimiter) {
171
+ return this.sendXmlResponse(res, {
172
+ ListBucketResult: {
173
+ IsTruncated: false,
174
+ Contents: objects.map((object) => ({
175
+ Key: object.path,
176
+ LastModified: new Date(object.mtimeMs).toISOString(),
177
+ Size: object.size.toString(),
178
+ ETag: `"${object.uuid || import_crypto.default.randomUUID()}"`,
179
+ StorageClass: "STANDARD"
180
+ })),
181
+ CommonPrefixes: commonPrefixes.map((prefix2) => ({
182
+ Prefix: prefix2
183
+ })),
184
+ KeyCount: objects.length.toString(),
185
+ Prefix: prefix,
186
+ Delimiter: delimiter,
187
+ MaxKeys: 1e3,
188
+ Name: bucket
189
+ }
190
+ });
191
+ }
192
+ /**
193
+ * 删除对象响应
194
+ * @param {object} res - Express响应对象
195
+ * @param {Array} deleted - 已删除的对象
196
+ * @param {Array} errors - 错误信息
197
+ */
198
+ async deleteObjects(res, deleted, errors) {
199
+ return this.sendXmlResponse(res, {
200
+ DeleteResult: {
201
+ Deleted: deleted.map((del) => ({
202
+ Key: del.Key
203
+ })),
204
+ Error: errors.map((error) => ({
205
+ Key: error.Key,
206
+ Code: error.Code,
207
+ Message: error.Message
208
+ }))
209
+ }
210
+ });
211
+ }
212
+ /**
213
+ * 获取存储桶位置
214
+ * @param {object} res - Express响应对象
215
+ * @param {string} region - 区域
216
+ */
217
+ async getBucketLocation(res, region) {
218
+ return this.sendXmlResponse(res, {
219
+ LocationConstraint: region || ""
220
+ });
221
+ }
222
+ /**
223
+ * 成功响应(200 OK)
224
+ * @param {object} res - Express响应对象
225
+ */
226
+ async ok(res) {
227
+ if (res.headersSent) {
228
+ return;
229
+ }
230
+ res.set("Content-Type", "application/xml; charset=utf-8");
231
+ res.set("Content-Length", "0");
232
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
233
+ res.status(200);
234
+ return new Promise((resolve) => {
235
+ res.end("", () => {
236
+ resolve();
237
+ });
238
+ });
239
+ }
240
+ /**
241
+ * 无内容响应(204 No Content)
242
+ * @param {object} res - Express响应对象
243
+ */
244
+ async noContent(res) {
245
+ if (res.headersSent) {
246
+ return;
247
+ }
248
+ res.set("Content-Type", "application/xml; charset=utf-8");
249
+ res.set("Content-Length", "0");
250
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
251
+ res.status(204);
252
+ return new Promise((resolve) => {
253
+ res.end("", () => {
254
+ resolve();
255
+ });
256
+ });
257
+ }
258
+ };
259
+ var responses_default = new Responses();
260
+
261
+ // src/handlers/createBucket.ts
262
+ var CreateBucket = class {
263
+ constructor(server) {
264
+ this.server = server;
265
+ this.handle = this.handle.bind(this);
266
+ }
267
+ /**
268
+ * 处理CreateBucket请求
269
+ * @param {object} req - Express请求对象
270
+ * @param {object} res - Express响应对象
271
+ */
272
+ async handle(req, res) {
273
+ try {
274
+ const { bucket } = this.extractBucketAndKey(req);
275
+ if (!bucket) {
276
+ await responses_default.error(
277
+ res,
278
+ 400,
279
+ "InvalidBucketName",
280
+ "\u65E0\u6548\u7684\u5B58\u50A8\u6876\u540D\u79F0"
281
+ );
282
+ return;
283
+ }
284
+ if (!this.isValidBucketName(bucket)) {
285
+ await responses_default.error(
286
+ res,
287
+ 400,
288
+ "InvalidBucketName",
289
+ "\u5B58\u50A8\u6876\u540D\u79F0\u4E0D\u7B26\u5408\u89C4\u8303"
290
+ );
291
+ return;
292
+ }
293
+ const bucketExists = await this.server.driver.bucketExists(bucket);
294
+ if (bucketExists) {
295
+ if (await this.server.driver.isBucketOwner(bucket)) {
296
+ res.set("Location", `/${bucket}`);
297
+ await responses_default.ok(res);
298
+ return;
299
+ }
300
+ await responses_default.error(
301
+ res,
302
+ 409,
303
+ "BucketAlreadyExists",
304
+ "\u5B58\u50A8\u6876\u5DF2\u5B58\u5728\u4E14\u4E0D\u5C5E\u4E8E\u60A8"
305
+ );
306
+ return;
307
+ }
308
+ await this.server.driver.createBucket(bucket);
309
+ res.set("Location", `/${bucket}`);
310
+ await responses_default.ok(res);
311
+ } catch (error) {
312
+ console.error("Error in CreateBucket:", error);
313
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
314
+ }
315
+ }
316
+ /**
317
+ * 从请求参数中提取存储桶和键
318
+ * @param {object} req - Express请求对象
319
+ * @returns {object} 包含存储桶和键的对象
320
+ */
321
+ extractBucketAndKey(req) {
322
+ const url = new URL(req.url, `http://${req.headers.host}`);
323
+ const pathParts = url.pathname.split("/").filter(Boolean);
324
+ return {
325
+ bucket: pathParts[0],
326
+ key: pathParts.slice(1).join("/")
327
+ };
328
+ }
329
+ /**
330
+ * 验证存储桶名称是否符合规范
331
+ * @param {string} name - 存储桶名称
332
+ * @returns {boolean} 是否有效
333
+ */
334
+ isValidBucketName(name) {
335
+ if (!name || name.length < 3 || name.length > 63) {
336
+ return false;
337
+ }
338
+ if (!/^[a-z0-9][a-z0-9.-]*[a-z0-9]$/.test(name)) {
339
+ return false;
340
+ }
341
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(name)) {
342
+ return false;
343
+ }
344
+ if (name.includes("..") || name.includes(".-") || name.includes("-.")) {
345
+ return false;
346
+ }
347
+ return true;
348
+ }
349
+ };
350
+
351
+ // src/handlers/deleteBucket.ts
352
+ var DeleteBucket = class {
353
+ constructor(server) {
354
+ this.server = server;
355
+ this.handle = this.handle.bind(this);
356
+ }
357
+ /**
358
+ * 处理DeleteBucket请求
359
+ * @param {object} req - Express请求对象
360
+ * @param {object} res - Express响应对象
361
+ */
362
+ async handle(req, res) {
363
+ try {
364
+ const { bucket } = this.extractBucketAndKey(req);
365
+ if (!bucket) {
366
+ await responses_default.error(
367
+ res,
368
+ 400,
369
+ "InvalidBucketName",
370
+ "\u65E0\u6548\u7684\u5B58\u50A8\u6876\u540D\u79F0"
371
+ );
372
+ return;
373
+ }
374
+ const bucketExists = await this.server.driver.bucketExists(bucket);
375
+ if (!bucketExists) {
376
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
377
+ return;
378
+ }
379
+ const isEmpty = await this.server.driver.isBucketEmpty(bucket);
380
+ if (!isEmpty) {
381
+ await responses_default.error(res, 409, "BucketNotEmpty", "\u5B58\u50A8\u6876\u4E0D\u4E3A\u7A7A");
382
+ return;
383
+ }
384
+ const isOwner = await this.server.driver.isBucketOwner(bucket);
385
+ if (!isOwner) {
386
+ await responses_default.error(res, 403, "AccessDenied", "\u6CA1\u6709\u6743\u9650\u5220\u9664\u6B64\u5B58\u50A8\u6876");
387
+ return;
388
+ }
389
+ await this.server.driver.deleteBucket(bucket);
390
+ await responses_default.noContent(res);
391
+ } catch (error) {
392
+ console.error("Error in DeleteBucket:", error);
393
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
394
+ }
395
+ }
396
+ /**
397
+ * 从请求参数中提取存储桶和键
398
+ * @param {object} req - Express请求对象
399
+ * @returns {object} 包含存储桶和键的对象
400
+ */
401
+ extractBucketAndKey(req) {
402
+ const url = new URL(req.url, `http://${req.headers.host}`);
403
+ const pathParts = url.pathname.split("/").filter(Boolean);
404
+ return {
405
+ bucket: pathParts[0],
406
+ key: pathParts.slice(1).join("/")
407
+ };
408
+ }
409
+ };
410
+
411
+ // src/handlers/deleteObject.ts
412
+ var DeleteObject = class {
413
+ constructor(server) {
414
+ this.server = server;
415
+ this.handle = this.handle.bind(this);
416
+ }
417
+ /**
418
+ * 处理DeleteObject请求
419
+ * @param {object} req - Express请求对象
420
+ * @param {object} res - Express响应对象
421
+ */
422
+ async handle(req, res) {
423
+ try {
424
+ const { bucket, key } = this.extractBucketAndKey(req);
425
+ if (!bucket) {
426
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
427
+ return;
428
+ }
429
+ if (!key) {
430
+ await responses_default.error(res, 400, "InvalidRequest", "\u65E0\u6548\u7684\u5BF9\u8C61\u952E");
431
+ return;
432
+ }
433
+ const objectPath = `/${bucket}/${key}`;
434
+ const stats = await this.server.driver.stat(objectPath);
435
+ if (!stats) {
436
+ await responses_default.noContent(res);
437
+ return;
438
+ }
439
+ if (stats.isDirectory) {
440
+ await responses_default.error(res, 400, "InvalidRequest", "\u8BF7\u6C42\u7684\u952E\u662F\u4E00\u4E2A\u76EE\u5F55");
441
+ return;
442
+ }
443
+ await this.server.driver.unlink(objectPath);
444
+ await responses_default.noContent(res);
445
+ } catch (error) {
446
+ console.error("Error in DeleteObject:", error);
447
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
448
+ }
449
+ }
450
+ /**
451
+ * 从请求参数中提取存储桶和键
452
+ * @param {object} req - Express请求对象
453
+ * @returns {object} 包含存储桶和键的对象
454
+ */
455
+ extractBucketAndKey(req) {
456
+ const url = new URL(req.url, `http://${req.headers.host}`);
457
+ const pathParts = url.pathname.split("/").filter(Boolean);
458
+ return {
459
+ bucket: pathParts[0],
460
+ key: pathParts.slice(1).join("/")
461
+ };
462
+ }
463
+ };
464
+
465
+ // src/handlers/deleteObjects.ts
466
+ var import_xml2js2 = require("xml2js");
467
+ var DeleteObjects = class {
468
+ constructor(server) {
469
+ this.server = server;
470
+ this.handle = this.handle.bind(this);
471
+ }
472
+ /**
473
+ * 处理DeleteObjects请求
474
+ * @param {object} req - Express请求对象
475
+ * @param {object} res - Express响应对象
476
+ */
477
+ async handle(req, res) {
478
+ try {
479
+ const { bucket } = this.extractBucketAndKey(req);
480
+ if (!bucket) {
481
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
482
+ return;
483
+ }
484
+ const bucketExists = await this.server.driver.bucketExists(bucket);
485
+ if (!bucketExists) {
486
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
487
+ return;
488
+ }
489
+ const body = await this.getRequestBody(req);
490
+ let deleteData;
491
+ try {
492
+ const parsedXml = await (0, import_xml2js2.parseStringPromise)(body);
493
+ deleteData = parsedXml.Delete;
494
+ } catch (error) {
495
+ await responses_default.error(res, 400, "MalformedXML", "XML\u683C\u5F0F\u9519\u8BEF");
496
+ return;
497
+ }
498
+ if (!deleteData || !deleteData.Object || !Array.isArray(deleteData.Object)) {
499
+ await responses_default.error(res, 400, "MalformedXML", "\u65E0\u6548\u7684\u5220\u9664\u8BF7\u6C42");
500
+ return;
501
+ }
502
+ const objectsToDelete = deleteData.Object.map((obj) => ({
503
+ Key: obj.Key[0]
504
+ }));
505
+ const deleted = [];
506
+ const errors = [];
507
+ for (const obj of objectsToDelete) {
508
+ try {
509
+ const objectPath = `/${bucket}/${obj.Key}`;
510
+ const stats = await this.server.driver.stat(objectPath);
511
+ if (!stats) {
512
+ deleted.push({ Key: obj.Key });
513
+ continue;
514
+ }
515
+ if (stats.isDirectory) {
516
+ errors.push({
517
+ Key: obj.Key,
518
+ Code: "InvalidRequest",
519
+ Message: "\u8BF7\u6C42\u7684\u952E\u662F\u4E00\u4E2A\u76EE\u5F55"
520
+ });
521
+ continue;
522
+ }
523
+ await this.server.driver.unlink(objectPath);
524
+ deleted.push({ Key: obj.Key });
525
+ } catch (error) {
526
+ errors.push({
527
+ Key: obj.Key,
528
+ Code: "InternalError",
529
+ Message: "\u5220\u9664\u5BF9\u8C61\u65F6\u53D1\u751F\u9519\u8BEF"
530
+ });
531
+ }
532
+ }
533
+ await responses_default.deleteObjects(res, deleted, errors);
534
+ } catch (error) {
535
+ console.error("Error in DeleteObjects:", error);
536
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
537
+ }
538
+ }
539
+ /**
540
+ * 从请求参数中提取存储桶和键
541
+ * @param {object} req - Express请求对象
542
+ * @returns {object} 包含存储桶和键的对象
543
+ */
544
+ extractBucketAndKey(req) {
545
+ const url = new URL(req.url, `http://${req.headers.host}`);
546
+ const pathParts = url.pathname.split("/").filter(Boolean);
547
+ return {
548
+ bucket: pathParts[0],
549
+ key: pathParts.slice(1).join("/")
550
+ };
551
+ }
552
+ /**
553
+ * 获取请求体内容
554
+ * @param {object} req - Express请求对象
555
+ * @returns {Promise<string>} 请求体内容
556
+ */
557
+ getRequestBody(req) {
558
+ return new Promise((resolve, reject) => {
559
+ let body = "";
560
+ req.on("data", (chunk) => {
561
+ body += chunk.toString();
562
+ });
563
+ req.on("end", () => {
564
+ resolve(body);
565
+ });
566
+ req.on("error", (error) => {
567
+ reject(error);
568
+ });
569
+ });
570
+ }
571
+ };
572
+
573
+ // src/handlers/getObject.ts
574
+ var import_crypto2 = __toESM(require("crypto"), 1);
575
+ var import_path = __toESM(require("path"), 1);
576
+ var GetObject = class {
577
+ constructor(server) {
578
+ this.server = server;
579
+ this.handle = this.handle.bind(this);
580
+ }
581
+ /**
582
+ * 处理GetObject请求
583
+ * @param {object} req - Express请求对象
584
+ * @param {object} res - Express响应对象
585
+ */
586
+ async handle(req, res) {
587
+ try {
588
+ const { bucket, key } = this.extractBucketAndKey(req);
589
+ if (!bucket) {
590
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
591
+ return;
592
+ }
593
+ if (!key) {
594
+ await responses_default.error(res, 400, "InvalidRequest", "\u65E0\u6548\u7684\u5BF9\u8C61\u952E");
595
+ return;
596
+ }
597
+ const objectPath = `/${bucket}/${key}`;
598
+ const stats = await this.server.driver.stat(objectPath);
599
+ if (!stats) {
600
+ await responses_default.error(res, 404, "NoSuchKey", "\u5BF9\u8C61\u4E0D\u5B58\u5728");
601
+ return;
602
+ }
603
+ if (stats.isDirectory) {
604
+ await responses_default.error(res, 400, "InvalidRequest", "\u8BF7\u6C42\u7684\u952E\u662F\u4E00\u4E2A\u76EE\u5F55");
605
+ return;
606
+ }
607
+ res.set("Content-Type", this.getContentType(key));
608
+ res.set("Content-Length", stats.size.toString());
609
+ res.set("Last-Modified", new Date(stats.mtime).toUTCString());
610
+ res.set("ETag", `"${stats.uuid || import_crypto2.default.randomUUID()}"`);
611
+ res.set("Accept-Ranges", "bytes");
612
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
613
+ if (req.headers.range) {
614
+ const range2 = this.parseRange(req.headers.range, stats.size);
615
+ if (range2) {
616
+ res.set(
617
+ "Content-Range",
618
+ `bytes ${range2.start}-${range2.end}/${stats.size}`
619
+ );
620
+ res.set("Content-Length", (range2.end - range2.start + 1).toString());
621
+ res.status(206);
622
+ } else {
623
+ res.set("Content-Range", `bytes */${stats.size}`);
624
+ res.status(416);
625
+ res.end();
626
+ return;
627
+ }
628
+ } else {
629
+ res.status(200);
630
+ }
631
+ const stream = await this.server.driver.createReadStream(objectPath, {
632
+ start: range?.start,
633
+ end: range?.end
634
+ });
635
+ stream.pipe(res);
636
+ stream.on("error", async (error) => {
637
+ console.error("Error streaming object:", error);
638
+ if (!res.headersSent) {
639
+ await responses_default.error(
640
+ res,
641
+ 500,
642
+ "InternalError",
643
+ "\u8BFB\u53D6\u5BF9\u8C61\u65F6\u53D1\u751F\u9519\u8BEF"
644
+ );
645
+ } else {
646
+ res.end();
647
+ }
648
+ });
649
+ } catch (error) {
650
+ console.error("Error in GetObject:", error);
651
+ if (!res.headersSent) {
652
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
653
+ }
654
+ }
655
+ }
656
+ /**
657
+ * 从请求参数中提取存储桶和键
658
+ * @param {object} req - Express请求对象
659
+ * @returns {object} 包含存储桶和键的对象
660
+ */
661
+ extractBucketAndKey(req) {
662
+ const url = new URL(req.url, `http://${req.headers.host}`);
663
+ const pathParts = url.pathname.split("/").filter(Boolean);
664
+ return {
665
+ bucket: pathParts[0],
666
+ key: pathParts.slice(1).join("/")
667
+ };
668
+ }
669
+ /**
670
+ * 根据文件扩展名获取内容类型
671
+ * @param {string} filename - 文件名
672
+ * @returns {string} 内容类型
673
+ */
674
+ getContentType(filename) {
675
+ const ext = import_path.default.extname(filename).toLowerCase();
676
+ const mimeTypes = {
677
+ ".html": "text/html",
678
+ ".htm": "text/html",
679
+ ".css": "text/css",
680
+ ".js": "application/javascript",
681
+ ".json": "application/json",
682
+ ".png": "image/png",
683
+ ".jpg": "image/jpeg",
684
+ ".jpeg": "image/jpeg",
685
+ ".gif": "image/gif",
686
+ ".webp": "image/webp",
687
+ ".svg": "image/svg+xml",
688
+ ".pdf": "application/pdf",
689
+ ".txt": "text/plain",
690
+ ".xml": "application/xml",
691
+ ".zip": "application/zip",
692
+ ".mp3": "audio/mpeg",
693
+ ".mp4": "video/mp4",
694
+ ".webm": "video/webm",
695
+ ".doc": "application/msword",
696
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
697
+ ".xls": "application/vnd.ms-excel",
698
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
699
+ ".ppt": "application/vnd.ms-powerpoint",
700
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
701
+ };
702
+ return mimeTypes[ext] || "application/octet-stream";
703
+ }
704
+ /**
705
+ * 解析Range头
706
+ * @param {string} rangeHeader - Range头值
707
+ * @param {number} size - 文件大小
708
+ * @returns {object|null} 范围对象或null
709
+ */
710
+ parseRange(rangeHeader, size) {
711
+ const matches = rangeHeader.match(/bytes=(\d+)-(\d*)/);
712
+ if (!matches) return null;
713
+ const start = parseInt(matches[1], 10);
714
+ const end = matches[2] ? parseInt(matches[2], 10) : size - 1;
715
+ if (isNaN(start) || isNaN(end) || start > end || start >= size || end >= size) {
716
+ return null;
717
+ }
718
+ return { start, end };
719
+ }
720
+ };
721
+
722
+ // src/handlers/headBucket.ts
723
+ var HeadBucket = class {
724
+ constructor(server) {
725
+ this.server = server;
726
+ this.handle = this.handle.bind(this);
727
+ }
728
+ /**
729
+ * 处理HeadBucket请求
730
+ * @param {object} req - Express请求对象
731
+ * @param {object} res - Express响应对象
732
+ */
733
+ async handle(req, res) {
734
+ try {
735
+ const { bucket } = this.extractBucketAndKey(req);
736
+ if (!bucket) {
737
+ res.status(404).end();
738
+ return;
739
+ }
740
+ const bucketExists = await this.server.driver.bucketExists(bucket);
741
+ if (!bucketExists) {
742
+ res.status(404).end();
743
+ return;
744
+ }
745
+ const hasAccess = await this.server.driver.hasBucketAccess(bucket);
746
+ if (!hasAccess) {
747
+ res.status(403).end();
748
+ return;
749
+ }
750
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
751
+ res.set("x-amz-bucket-region", this.server.config.region || "default");
752
+ res.status(200).end();
753
+ } catch (error) {
754
+ console.error("Error in HeadBucket:", error);
755
+ res.status(500).end();
756
+ }
757
+ }
758
+ /**
759
+ * 从请求参数中提取存储桶和键
760
+ * @param {object} req - Express请求对象
761
+ * @returns {object} 包含存储桶和键的对象
762
+ */
763
+ extractBucketAndKey(req) {
764
+ const url = new URL(req.url, `http://${req.headers.host}`);
765
+ const pathParts = url.pathname.split("/").filter(Boolean);
766
+ return {
767
+ bucket: pathParts[0],
768
+ key: pathParts.slice(1).join("/")
769
+ };
770
+ }
771
+ };
772
+
773
+ // src/handlers/headObject.ts
774
+ var import_crypto3 = __toESM(require("crypto"), 1);
775
+ var import_path2 = __toESM(require("path"), 1);
776
+ var HeadObject = class {
777
+ constructor(server) {
778
+ this.server = server;
779
+ this.handle = this.handle.bind(this);
780
+ }
781
+ /**
782
+ * 处理HeadObject请求
783
+ * @param {object} req - Express请求对象
784
+ * @param {object} res - Express响应对象
785
+ */
786
+ async handle(req, res) {
787
+ try {
788
+ const { bucket, key } = this.extractBucketAndKey(req);
789
+ if (!bucket) {
790
+ res.status(404).end();
791
+ return;
792
+ }
793
+ if (!key) {
794
+ res.status(400).end();
795
+ return;
796
+ }
797
+ const objectPath = `/${bucket}/${key}`;
798
+ const stats = await this.server.driver.stat(objectPath);
799
+ if (!stats) {
800
+ res.status(404).end();
801
+ return;
802
+ }
803
+ if (stats.isDirectory) {
804
+ res.status(400).end();
805
+ return;
806
+ }
807
+ res.set("Content-Type", this.getContentType(key));
808
+ res.set("Content-Length", stats.size.toString());
809
+ res.set("Last-Modified", new Date(stats.mtime).toUTCString());
810
+ res.set("ETag", `"${stats.uuid || import_crypto3.default.randomUUID()}"`);
811
+ res.set("Accept-Ranges", "bytes");
812
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
813
+ const metadata = await this.server.driver.getObjectMetadata(objectPath);
814
+ if (metadata) {
815
+ Object.entries(metadata).forEach(([key2, value]) => {
816
+ if (key2.startsWith("x-amz-meta-")) {
817
+ res.set(key2, value);
818
+ }
819
+ });
820
+ }
821
+ res.status(200).end();
822
+ } catch (error) {
823
+ console.error("Error in HeadObject:", error);
824
+ res.status(500).end();
825
+ }
826
+ }
827
+ /**
828
+ * 从请求参数中提取存储桶和键
829
+ * @param {object} req - Express请求对象
830
+ * @returns {object} 包含存储桶和键的对象
831
+ */
832
+ extractBucketAndKey(req) {
833
+ const url = new URL(req.url, `http://${req.headers.host}`);
834
+ const pathParts = url.pathname.split("/").filter(Boolean);
835
+ return {
836
+ bucket: pathParts[0],
837
+ key: pathParts.slice(1).join("/")
838
+ };
839
+ }
840
+ /**
841
+ * 根据文件扩展名获取内容类型
842
+ * @param {string} filename - 文件名
843
+ * @returns {string} 内容类型
844
+ */
845
+ getContentType(filename) {
846
+ const ext = import_path2.default.extname(filename).toLowerCase();
847
+ const mimeTypes = {
848
+ ".html": "text/html",
849
+ ".htm": "text/html",
850
+ ".css": "text/css",
851
+ ".js": "application/javascript",
852
+ ".json": "application/json",
853
+ ".png": "image/png",
854
+ ".jpg": "image/jpeg",
855
+ ".jpeg": "image/jpeg",
856
+ ".gif": "image/gif",
857
+ ".webp": "image/webp",
858
+ ".svg": "image/svg+xml",
859
+ ".pdf": "application/pdf",
860
+ ".txt": "text/plain",
861
+ ".xml": "application/xml",
862
+ ".zip": "application/zip",
863
+ ".mp3": "audio/mpeg",
864
+ ".mp4": "video/mp4",
865
+ ".webm": "video/webm",
866
+ ".doc": "application/msword",
867
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
868
+ ".xls": "application/vnd.ms-excel",
869
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
870
+ ".ppt": "application/vnd.ms-powerpoint",
871
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
872
+ };
873
+ return mimeTypes[ext] || "application/octet-stream";
874
+ }
875
+ };
876
+
877
+ // src/handlers/listBuckets.ts
878
+ var ListBuckets = class {
879
+ constructor(server) {
880
+ this.server = server;
881
+ this.handle = this.handle.bind(this);
882
+ }
883
+ /**
884
+ * 处理ListBuckets请求
885
+ * @param {object} req - Express请求对象
886
+ * @param {object} res - Express响应对象
887
+ */
888
+ async handle(req, res) {
889
+ try {
890
+ const buckets = await this.server.driver.listBuckets();
891
+ const owner = {
892
+ id: this.server.config.accessKeyId || "anonymous",
893
+ displayName: this.server.config.displayName || "S3 User"
894
+ };
895
+ await responses_default.listBuckets(res, buckets, owner);
896
+ } catch (error) {
897
+ console.error("Error in ListBuckets:", error);
898
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
899
+ }
900
+ }
901
+ };
902
+
903
+ // src/handlers/listObjects.ts
904
+ var import_crypto4 = __toESM(require("crypto"), 1);
905
+ var import_path3 = __toESM(require("path"), 1);
906
+ var ListObjects = class {
907
+ constructor(server) {
908
+ this.server = server;
909
+ this.handle = this.handle.bind(this);
910
+ }
911
+ /**
912
+ * 解析请求查询参数
913
+ * @param {object} req - Express请求对象
914
+ * @returns {object} 解析后的参数
915
+ */
916
+ parseQueryParams(req) {
917
+ if (!req || !req.query) {
918
+ return { prefix: "", delimiter: "" };
919
+ }
920
+ return {
921
+ prefix: typeof req.query.prefix === "string" && req.query.prefix.length > 0 ? req.query.prefix : "",
922
+ delimiter: typeof req.query.delimiter === "string" && req.query.delimiter.length > 0 ? req.query.delimiter : ""
923
+ };
924
+ }
925
+ /**
926
+ * 规范化前缀
927
+ * @param {string} prefix - 原始前缀
928
+ * @returns {string} 规范化后的前缀
929
+ */
930
+ normalizePrefix(prefix) {
931
+ let trimmed = decodeURIComponent(prefix).trim();
932
+ if (trimmed.length === 0 || trimmed === "/" || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.includes("../")) {
933
+ return "/";
934
+ }
935
+ if (!trimmed.startsWith("/")) {
936
+ trimmed = `/${trimmed}`;
937
+ }
938
+ return trimmed;
939
+ }
940
+ /**
941
+ * 处理ListObjects请求
942
+ * @param {object} req - Express请求对象
943
+ * @param {object} res - Express响应对象
944
+ */
945
+ async handle(req, res) {
946
+ try {
947
+ if (req.url.includes("?location")) {
948
+ await responses_default.getBucketLocation(
949
+ res,
950
+ this.server.config.region || "default"
951
+ );
952
+ return;
953
+ }
954
+ const { bucket } = this.extractBucketAndKey(req);
955
+ if (!bucket) {
956
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
957
+ return;
958
+ }
959
+ const params = this.parseQueryParams(req);
960
+ const normalizedPrefix = this.normalizePrefix(params.prefix);
961
+ let dirname = this.normalizePrefix(
962
+ normalizedPrefix === "/" ? `/${bucket}` : import_path3.default.posix.dirname(import_path3.default.posix.join(bucket, normalizedPrefix))
963
+ );
964
+ const requestedPath = this.normalizePrefix(
965
+ import_path3.default.posix.join(bucket, normalizedPrefix)
966
+ );
967
+ const requestedPathStats = await this.server.driver.stat(requestedPath);
968
+ if (requestedPathStats && requestedPathStats.isDirectory) {
969
+ dirname = requestedPath;
970
+ }
971
+ const dirnameStats = await this.server.driver.stat(dirname);
972
+ if (!dirnameStats) {
973
+ await responses_default.listObjectsV2(
974
+ res,
975
+ params.prefix,
976
+ [],
977
+ [],
978
+ bucket,
979
+ params.delimiter
980
+ );
981
+ return;
982
+ }
983
+ const depth = params.delimiter.length === 0 ? 10 : 0;
984
+ const dirContent = await this.readdir(dirname, depth);
985
+ const objects = [];
986
+ const commonPrefixes = [];
987
+ for (const item of dirContent) {
988
+ const itemPath = import_path3.default.posix.join(dirname, item);
989
+ const itemStats = await this.server.driver.stat(itemPath);
990
+ if (!itemStats) continue;
991
+ const relativePath = itemPath.startsWith(`/${bucket}/`) ? itemPath.slice(`/${bucket}/`.length) : itemPath;
992
+ if (itemStats.isDirectory) {
993
+ commonPrefixes.push(`${relativePath}/`);
994
+ } else {
995
+ objects.push({
996
+ path: relativePath,
997
+ mtimeMs: itemStats.mtime || Date.now(),
998
+ size: itemStats.size || 0,
999
+ uuid: itemStats.uuid || import_crypto4.default.randomUUID()
1000
+ });
1001
+ }
1002
+ }
1003
+ await responses_default.listObjectsV2(
1004
+ res,
1005
+ normalizedPrefix === "/" ? "/" : normalizedPrefix.slice(1),
1006
+ objects,
1007
+ commonPrefixes,
1008
+ bucket,
1009
+ params.delimiter
1010
+ );
1011
+ } catch (error) {
1012
+ console.error("Error in ListObjects:", error);
1013
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
1014
+ }
1015
+ }
1016
+ /**
1017
+ * 从请求参数中提取存储桶和键
1018
+ * @param {object} req - Express请求对象
1019
+ * @returns {object} 包含存储桶和键的对象
1020
+ */
1021
+ extractBucketAndKey(req) {
1022
+ const url = new URL(req.url, `http://${req.headers.host}`);
1023
+ const pathParts = url.pathname.split("/").filter(Boolean);
1024
+ return {
1025
+ bucket: pathParts[0],
1026
+ key: pathParts.slice(1).join("/")
1027
+ };
1028
+ }
1029
+ /**
1030
+ * 读取目录内容
1031
+ * @param {string} dirPath - 目录路径
1032
+ * @param {number} depth - 递归深度
1033
+ * @returns {Promise<Array>} 目录内容列表
1034
+ */
1035
+ async readdir(dirPath, depth) {
1036
+ try {
1037
+ if (depth <= 0) {
1038
+ return await this.server.driver.readdir(dirPath);
1039
+ }
1040
+ const items = [];
1041
+ const traverse = async (currentPath, currentDepth) => {
1042
+ if (currentDepth >= depth) {
1043
+ return;
1044
+ }
1045
+ const entries = await this.server.driver.readdir(currentPath);
1046
+ for (const entry of entries) {
1047
+ const entryPath = import_path3.default.posix.join(currentPath, entry);
1048
+ const stats = await this.server.driver.stat(entryPath);
1049
+ items.push(entryPath.slice(dirPath.length));
1050
+ if (stats && stats.isDirectory) {
1051
+ await traverse(entryPath, currentDepth + 1);
1052
+ }
1053
+ }
1054
+ };
1055
+ await traverse(dirPath, 0);
1056
+ return items;
1057
+ } catch (error) {
1058
+ console.error("Error reading directory:", error);
1059
+ return [];
1060
+ }
1061
+ }
1062
+ };
1063
+
1064
+ // src/handlers/putObject.ts
1065
+ var import_crypto5 = __toESM(require("crypto"), 1);
1066
+ var PutObject = class {
1067
+ constructor(server) {
1068
+ this.server = server;
1069
+ this.handle = this.handle.bind(this);
1070
+ }
1071
+ /**
1072
+ * 处理PutObject请求
1073
+ * @param {object} req - Express请求对象
1074
+ * @param {object} res - Express响应对象
1075
+ */
1076
+ async handle(req, res) {
1077
+ try {
1078
+ const { bucket, key } = this.extractBucketAndKey(req);
1079
+ if (!bucket) {
1080
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
1081
+ return;
1082
+ }
1083
+ if (!key) {
1084
+ await responses_default.error(res, 400, "InvalidRequest", "\u65E0\u6548\u7684\u5BF9\u8C61\u952E");
1085
+ return;
1086
+ }
1087
+ const bucketExists = await this.server.driver.bucketExists(bucket);
1088
+ if (!bucketExists) {
1089
+ await responses_default.error(res, 404, "NoSuchBucket", "\u5B58\u50A8\u6876\u4E0D\u5B58\u5728");
1090
+ return;
1091
+ }
1092
+ const objectPath = `/${bucket}/${key}`;
1093
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
1094
+ if (isNaN(contentLength) || contentLength < 0) {
1095
+ await responses_default.error(
1096
+ res,
1097
+ 400,
1098
+ "InvalidRequest",
1099
+ "\u65E0\u6548\u7684Content-Length"
1100
+ );
1101
+ return;
1102
+ }
1103
+ const writeStream = await this.server.driver.createWriteStream(objectPath);
1104
+ const md5Hash = import_crypto5.default.createHash("md5");
1105
+ let bytesWritten = 0;
1106
+ await new Promise((resolve, reject) => {
1107
+ let settled = false;
1108
+ const rejectOnce = (error) => {
1109
+ if (settled) return;
1110
+ settled = true;
1111
+ writeStream.destroy();
1112
+ reject(error);
1113
+ };
1114
+ const complete = async () => {
1115
+ if (settled) return;
1116
+ settled = true;
1117
+ try {
1118
+ const etag = md5Hash.digest("hex");
1119
+ await this.server.driver.updateObjectMetadata(objectPath, {
1120
+ etag,
1121
+ contentType: req.headers["content-type"] || "application/octet-stream",
1122
+ contentLength: bytesWritten,
1123
+ lastModified: /* @__PURE__ */ new Date()
1124
+ });
1125
+ res.set("ETag", `"${etag}"`);
1126
+ res.set("Date", (/* @__PURE__ */ new Date()).toUTCString());
1127
+ res.status(200).end();
1128
+ resolve();
1129
+ } catch (error) {
1130
+ reject(error);
1131
+ }
1132
+ };
1133
+ writeStream.on("error", rejectOnce);
1134
+ const rawBody = req.body;
1135
+ if (Buffer.isBuffer(rawBody) || rawBody instanceof Uint8Array) {
1136
+ const chunk = Buffer.from(rawBody);
1137
+ if (chunk.length > 0) {
1138
+ writeStream.write(chunk);
1139
+ md5Hash.update(chunk);
1140
+ bytesWritten += chunk.length;
1141
+ }
1142
+ writeStream.end(() => void complete());
1143
+ return;
1144
+ }
1145
+ if (typeof rawBody === "string") {
1146
+ const chunk = Buffer.from(rawBody);
1147
+ if (chunk.length > 0) {
1148
+ writeStream.write(chunk);
1149
+ md5Hash.update(chunk);
1150
+ bytesWritten += chunk.length;
1151
+ }
1152
+ writeStream.end(() => void complete());
1153
+ return;
1154
+ }
1155
+ req.on("data", (chunk) => {
1156
+ writeStream.write(chunk);
1157
+ md5Hash.update(chunk);
1158
+ bytesWritten += chunk.length;
1159
+ });
1160
+ req.on("end", () => {
1161
+ writeStream.end(() => void complete());
1162
+ });
1163
+ req.on("error", rejectOnce);
1164
+ });
1165
+ } catch (error) {
1166
+ console.error("Error in PutObject:", error);
1167
+ if (!res.headersSent) {
1168
+ await responses_default.error(res, 500, "InternalError", "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
1169
+ }
1170
+ }
1171
+ }
1172
+ /**
1173
+ * 从请求参数中提取存储桶和键
1174
+ * @param {object} req - Express请求对象
1175
+ * @returns {object} 包含存储桶和键的对象
1176
+ */
1177
+ extractBucketAndKey(req) {
1178
+ const url = new URL(req.url, `http://${req.headers.host}`);
1179
+ const pathParts = url.pathname.split("/").filter(Boolean);
1180
+ return {
1181
+ bucket: pathParts[0],
1182
+ key: pathParts.slice(1).join("/")
1183
+ };
1184
+ }
1185
+ };
1186
+
1187
+ // src/index.ts
1188
+ var S3Server = class {
1189
+ /**
1190
+ * 创建S3服务器实例
1191
+ * @param {Object} config - 服务器配置
1192
+ * @param {Object} driver - 存储驱动
1193
+ */
1194
+ constructor(config, driver) {
1195
+ this.config = {
1196
+ hostname: config.hostname || "0.0.0.0",
1197
+ port: config.port || 9e3,
1198
+ https: config.https || false,
1199
+ region: config.region || "default",
1200
+ accessKeyId: config.accessKeyId || "accessKey1",
1201
+ secretAccessKey: config.secretAccessKey || "verySecretKey1",
1202
+ displayName: config.displayName || "S3 User"
1203
+ };
1204
+ this.driver = driver;
1205
+ this.app = (0, import_express.default)();
1206
+ this.server = null;
1207
+ this.handlers = {
1208
+ listBuckets: new ListBuckets(this),
1209
+ listObjects: new ListObjects(this),
1210
+ getObject: new GetObject(this),
1211
+ putObject: new PutObject(this),
1212
+ deleteObject: new DeleteObject(this),
1213
+ deleteObjects: new DeleteObjects(this),
1214
+ headObject: new HeadObject(this),
1215
+ headBucket: new HeadBucket(this),
1216
+ createBucket: new CreateBucket(this),
1217
+ deleteBucket: new DeleteBucket(this)
1218
+ };
1219
+ this.setupMiddleware();
1220
+ this.setupRoutes();
1221
+ }
1222
+ /**
1223
+ * 配置Express中间件
1224
+ */
1225
+ setupMiddleware() {
1226
+ this.app.use(
1227
+ import_body_parser.default.raw({
1228
+ type: "*/*",
1229
+ limit: "10gb"
1230
+ })
1231
+ );
1232
+ this.app.use((req, res, next) => {
1233
+ const ctx = context_default(req);
1234
+ req.s3Context = ctx;
1235
+ if (this.config.accessKeyId && this.config.secretAccessKey) {
1236
+ const { accessKeyId, secretAccessKey } = ctx.auth;
1237
+ if (accessKeyId !== this.config.accessKeyId || secretAccessKey !== this.config.secretAccessKey) {
1238
+ res.status(403).send({
1239
+ Error: {
1240
+ Code: "AccessDenied",
1241
+ Message: "\u8BBF\u95EE\u88AB\u62D2\u7EDD"
1242
+ }
1243
+ });
1244
+ return;
1245
+ }
1246
+ }
1247
+ next();
1248
+ });
1249
+ this.app.use((req, res, next) => {
1250
+ console.log(`${(/* @__PURE__ */ new Date()).toISOString()} - ${req.method} ${req.url}`);
1251
+ next();
1252
+ });
1253
+ }
1254
+ /**
1255
+ * 配置API路由
1256
+ */
1257
+ setupRoutes() {
1258
+ this.app.get("/", (req, res) => {
1259
+ this.handlers.listBuckets.handle(req, res);
1260
+ });
1261
+ this.app.head("/:bucket", (req, res) => {
1262
+ this.handlers.headBucket.handle(req, res);
1263
+ });
1264
+ this.app.get("/:bucket", (req, res) => {
1265
+ this.handlers.listObjects.handle(req, res);
1266
+ });
1267
+ this.app.put("/:bucket", (req, res) => {
1268
+ this.handlers.createBucket.handle(req, res);
1269
+ });
1270
+ this.app.delete("/:bucket", (req, res) => {
1271
+ this.handlers.deleteBucket.handle(req, res);
1272
+ });
1273
+ this.app.head("/:bucket/*", (req, res) => {
1274
+ this.handlers.headObject.handle(req, res);
1275
+ });
1276
+ this.app.get("/:bucket/*", (req, res) => {
1277
+ this.handlers.getObject.handle(req, res);
1278
+ });
1279
+ this.app.put("/:bucket/*", (req, res) => {
1280
+ this.handlers.putObject.handle(req, res);
1281
+ });
1282
+ this.app.delete("/:bucket/*", (req, res) => {
1283
+ this.handlers.deleteObject.handle(req, res);
1284
+ });
1285
+ this.app.post("/:bucket", (req, res) => {
1286
+ if (req.query.delete !== void 0) {
1287
+ this.handlers.deleteObjects.handle(req, res);
1288
+ } else {
1289
+ res.status(400).send({
1290
+ Error: {
1291
+ Code: "InvalidRequest",
1292
+ Message: "\u65E0\u6548\u7684\u8BF7\u6C42"
1293
+ }
1294
+ });
1295
+ }
1296
+ });
1297
+ this.app.use((req, res) => {
1298
+ res.status(404).send({
1299
+ Error: {
1300
+ Code: "NotFound",
1301
+ Message: "\u8BF7\u6C42\u7684\u8D44\u6E90\u4E0D\u5B58\u5728"
1302
+ }
1303
+ });
1304
+ });
1305
+ this.app.use((err, req, res, next) => {
1306
+ console.error("Server error:", err);
1307
+ res.status(500).send({
1308
+ Error: {
1309
+ Code: "InternalError",
1310
+ Message: "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF"
1311
+ }
1312
+ });
1313
+ });
1314
+ }
1315
+ /**
1316
+ * 启动服务器
1317
+ * @returns {Promise<void>}
1318
+ */
1319
+ async start() {
1320
+ return new Promise((resolve, reject) => {
1321
+ try {
1322
+ if (this.config.https) {
1323
+ const options = {
1324
+ key: "...",
1325
+ // 私钥
1326
+ cert: "..."
1327
+ // 证书
1328
+ };
1329
+ this.server = import_https.default.createServer(options, this.app);
1330
+ } else {
1331
+ this.server = import_http.default.createServer(this.app);
1332
+ }
1333
+ this.server.listen(this.config.port, this.config.hostname, () => {
1334
+ console.log(
1335
+ `S3\u670D\u52A1\u5668\u5DF2\u542F\u52A8: ${this.config.https ? "https" : "http"}://${this.config.hostname}:${this.config.port}`
1336
+ );
1337
+ resolve();
1338
+ });
1339
+ this.server.on("error", (err) => {
1340
+ console.error("\u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25:", err);
1341
+ reject(err);
1342
+ });
1343
+ } catch (error) {
1344
+ console.error("\u542F\u52A8\u670D\u52A1\u5668\u65F6\u53D1\u751F\u9519\u8BEF:", error);
1345
+ reject(error);
1346
+ }
1347
+ });
1348
+ }
1349
+ /**
1350
+ * 停止服务器
1351
+ * @returns {Promise<void>}
1352
+ */
1353
+ async stop() {
1354
+ return new Promise((resolve, reject) => {
1355
+ if (!this.server) {
1356
+ resolve();
1357
+ return;
1358
+ }
1359
+ this.server.close((err) => {
1360
+ if (err) {
1361
+ console.error("\u505C\u6B62\u670D\u52A1\u5668\u65F6\u53D1\u751F\u9519\u8BEF:", err);
1362
+ reject(err);
1363
+ return;
1364
+ }
1365
+ console.log("S3\u670D\u52A1\u5668\u5DF2\u505C\u6B62");
1366
+ this.server = null;
1367
+ resolve();
1368
+ });
1369
+ });
1370
+ }
1371
+ };
1372
+ module.exports = module.exports.default; module.exports.default = module.exports;