@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.
@@ -1,77 +0,0 @@
1
- const Responses = require("../responses");
2
-
3
- /**
4
- * 处理S3 DeleteObject请求
5
- * 从存储桶中删除对象
6
- */
7
- class DeleteObject {
8
- constructor(server) {
9
- this.server = server;
10
- this.handle = this.handle.bind(this);
11
- }
12
-
13
- /**
14
- * 处理DeleteObject请求
15
- * @param {object} req - Express请求对象
16
- * @param {object} res - Express响应对象
17
- */
18
- async handle(req, res) {
19
- try {
20
- // 获取存储桶和键
21
- const { bucket, key } = this.extractBucketAndKey(req);
22
-
23
- if (!bucket) {
24
- await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
25
- return;
26
- }
27
-
28
- if (!key) {
29
- await Responses.error(res, 400, "InvalidRequest", "无效的对象键");
30
- return;
31
- }
32
-
33
- // 构建对象路径
34
- const objectPath = `/${bucket}/${key}`;
35
-
36
- // 检查对象是否存在
37
- const stats = await this.server.driver.stat(objectPath);
38
-
39
- if (!stats) {
40
- // S3兼容性:即使对象不存在也返回成功
41
- await Responses.noContent(res);
42
- return;
43
- }
44
-
45
- if (stats.isDirectory) {
46
- await Responses.error(res, 400, "InvalidRequest", "请求的键是一个目录");
47
- return;
48
- }
49
-
50
- // 删除对象
51
- await this.server.driver.unlink(objectPath);
52
-
53
- // 返回成功响应
54
- await Responses.noContent(res);
55
- } catch (error) {
56
- console.error("Error in DeleteObject:", error);
57
- await Responses.error(res, 500, "InternalError", "服务器内部错误");
58
- }
59
- }
60
-
61
- /**
62
- * 从请求参数中提取存储桶和键
63
- * @param {object} req - Express请求对象
64
- * @returns {object} 包含存储桶和键的对象
65
- */
66
- extractBucketAndKey(req) {
67
- const url = new URL(req.url, `http://${req.headers.host}`);
68
- const pathParts = url.pathname.split("/").filter(Boolean);
69
-
70
- return {
71
- bucket: pathParts[0],
72
- key: pathParts.slice(1).join("/"),
73
- };
74
- }
75
- }
76
-
77
- module.exports = DeleteObject;
@@ -1,148 +0,0 @@
1
- const Responses = require("../responses");
2
- const { parseStringPromise } = require("xml2js");
3
-
4
- /**
5
- * 处理S3 DeleteObjects请求
6
- * 批量删除多个对象
7
- */
8
- class DeleteObjects {
9
- constructor(server) {
10
- this.server = server;
11
- this.handle = this.handle.bind(this);
12
- }
13
-
14
- /**
15
- * 处理DeleteObjects请求
16
- * @param {object} req - Express请求对象
17
- * @param {object} res - Express响应对象
18
- */
19
- async handle(req, res) {
20
- try {
21
- // 获取存储桶名称
22
- const { bucket } = this.extractBucketAndKey(req);
23
-
24
- if (!bucket) {
25
- await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
26
- return;
27
- }
28
-
29
- // 检查存储桶是否存在
30
- const bucketExists = await this.server.driver.bucketExists(bucket);
31
-
32
- if (!bucketExists) {
33
- await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
34
- return;
35
- }
36
-
37
- // 解析请求体中的XML
38
- const body = await this.getRequestBody(req);
39
- let deleteData;
40
-
41
- try {
42
- const parsedXml = await parseStringPromise(body);
43
- deleteData = parsedXml.Delete;
44
- } catch (error) {
45
- await Responses.error(res, 400, "MalformedXML", "XML格式错误");
46
- return;
47
- }
48
-
49
- if (
50
- !deleteData ||
51
- !deleteData.Object ||
52
- !Array.isArray(deleteData.Object)
53
- ) {
54
- await Responses.error(res, 400, "MalformedXML", "无效的删除请求");
55
- return;
56
- }
57
-
58
- // 提取要删除的对象键
59
- const objectsToDelete = deleteData.Object.map((obj) => ({
60
- Key: obj.Key[0],
61
- }));
62
-
63
- // 批量删除对象
64
- const deleted = [];
65
- const errors = [];
66
-
67
- for (const obj of objectsToDelete) {
68
- try {
69
- const objectPath = `/${bucket}/${obj.Key}`;
70
-
71
- // 检查对象是否存在
72
- const stats = await this.server.driver.stat(objectPath);
73
-
74
- if (!stats) {
75
- // 对象不存在,但按照S3规范,仍然视为成功删除
76
- deleted.push({ Key: obj.Key });
77
- continue;
78
- }
79
-
80
- if (stats.isDirectory) {
81
- errors.push({
82
- Key: obj.Key,
83
- Code: "InvalidRequest",
84
- Message: "请求的键是一个目录",
85
- });
86
- continue;
87
- }
88
-
89
- // 删除对象
90
- await this.server.driver.unlink(objectPath);
91
- deleted.push({ Key: obj.Key });
92
- } catch (error) {
93
- errors.push({
94
- Key: obj.Key,
95
- Code: "InternalError",
96
- Message: "删除对象时发生错误",
97
- });
98
- }
99
- }
100
-
101
- // 返回删除结果
102
- await Responses.deleteObjects(res, deleted, errors);
103
- } catch (error) {
104
- console.error("Error in DeleteObjects:", error);
105
- await Responses.error(res, 500, "InternalError", "服务器内部错误");
106
- }
107
- }
108
-
109
- /**
110
- * 从请求参数中提取存储桶和键
111
- * @param {object} req - Express请求对象
112
- * @returns {object} 包含存储桶和键的对象
113
- */
114
- extractBucketAndKey(req) {
115
- const url = new URL(req.url, `http://${req.headers.host}`);
116
- const pathParts = url.pathname.split("/").filter(Boolean);
117
-
118
- return {
119
- bucket: pathParts[0],
120
- key: pathParts.slice(1).join("/"),
121
- };
122
- }
123
-
124
- /**
125
- * 获取请求体内容
126
- * @param {object} req - Express请求对象
127
- * @returns {Promise<string>} 请求体内容
128
- */
129
- getRequestBody(req) {
130
- return new Promise((resolve, reject) => {
131
- let body = "";
132
-
133
- req.on("data", (chunk) => {
134
- body += chunk.toString();
135
- });
136
-
137
- req.on("end", () => {
138
- resolve(body);
139
- });
140
-
141
- req.on("error", (error) => {
142
- reject(error);
143
- });
144
- });
145
- }
146
- }
147
-
148
- module.exports = DeleteObjects;
@@ -1,195 +0,0 @@
1
- const Responses = require("../responses");
2
- const path = require("path");
3
- const crypto = require("crypto");
4
-
5
- /**
6
- * 处理S3 GetObject请求
7
- * 获取对象内容
8
- */
9
- class GetObject {
10
- constructor(server) {
11
- this.server = server;
12
- this.handle = this.handle.bind(this);
13
- }
14
-
15
- /**
16
- * 处理GetObject请求
17
- * @param {object} req - Express请求对象
18
- * @param {object} res - Express响应对象
19
- */
20
- async handle(req, res) {
21
- try {
22
- // 获取存储桶和键
23
- const { bucket, key } = this.extractBucketAndKey(req);
24
-
25
- if (!bucket) {
26
- await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
27
- return;
28
- }
29
-
30
- if (!key) {
31
- await Responses.error(res, 400, "InvalidRequest", "无效的对象键");
32
- return;
33
- }
34
-
35
- // 构建对象路径
36
- const objectPath = `/${bucket}/${key}`;
37
-
38
- // 获取对象信息
39
- const stats = await this.server.driver.stat(objectPath);
40
-
41
- if (!stats) {
42
- await Responses.error(res, 404, "NoSuchKey", "对象不存在");
43
- return;
44
- }
45
-
46
- if (stats.isDirectory) {
47
- await Responses.error(res, 400, "InvalidRequest", "请求的键是一个目录");
48
- return;
49
- }
50
-
51
- // 设置响应头
52
- res.set("Content-Type", this.getContentType(key));
53
- res.set("Content-Length", stats.size.toString());
54
- res.set("Last-Modified", new Date(stats.mtime).toUTCString());
55
- res.set("ETag", `"${stats.uuid || crypto.randomUUID()}"`);
56
- res.set("Accept-Ranges", "bytes");
57
- res.set("Date", new Date().toUTCString());
58
-
59
- // 处理范围请求
60
- if (req.headers.range) {
61
- const range = this.parseRange(req.headers.range, stats.size);
62
-
63
- if (range) {
64
- res.set(
65
- "Content-Range",
66
- `bytes ${range.start}-${range.end}/${stats.size}`,
67
- );
68
- res.set("Content-Length", (range.end - range.start + 1).toString());
69
- res.status(206); // Partial Content
70
- } else {
71
- res.set("Content-Range", `bytes */${stats.size}`);
72
- res.status(416); // Range Not Satisfiable
73
- res.end();
74
- return;
75
- }
76
- } else {
77
- res.status(200);
78
- }
79
-
80
- // 流式传输对象内容
81
- const stream = await this.server.driver.createReadStream(objectPath, {
82
- start: range?.start,
83
- end: range?.end,
84
- });
85
-
86
- stream.pipe(res);
87
-
88
- // 处理错误
89
- stream.on("error", async (error) => {
90
- console.error("Error streaming object:", error);
91
- if (!res.headersSent) {
92
- await Responses.error(
93
- res,
94
- 500,
95
- "InternalError",
96
- "读取对象时发生错误",
97
- );
98
- } else {
99
- res.end();
100
- }
101
- });
102
- } catch (error) {
103
- console.error("Error in GetObject:", error);
104
- if (!res.headersSent) {
105
- await Responses.error(res, 500, "InternalError", "服务器内部错误");
106
- }
107
- }
108
- }
109
-
110
- /**
111
- * 从请求参数中提取存储桶和键
112
- * @param {object} req - Express请求对象
113
- * @returns {object} 包含存储桶和键的对象
114
- */
115
- extractBucketAndKey(req) {
116
- const url = new URL(req.url, `http://${req.headers.host}`);
117
- const pathParts = url.pathname.split("/").filter(Boolean);
118
-
119
- return {
120
- bucket: pathParts[0],
121
- key: pathParts.slice(1).join("/"),
122
- };
123
- }
124
-
125
- /**
126
- * 根据文件扩展名获取内容类型
127
- * @param {string} filename - 文件名
128
- * @returns {string} 内容类型
129
- */
130
- getContentType(filename) {
131
- const ext = path.extname(filename).toLowerCase();
132
-
133
- const mimeTypes = {
134
- ".html": "text/html",
135
- ".htm": "text/html",
136
- ".css": "text/css",
137
- ".js": "application/javascript",
138
- ".json": "application/json",
139
- ".png": "image/png",
140
- ".jpg": "image/jpeg",
141
- ".jpeg": "image/jpeg",
142
- ".gif": "image/gif",
143
- ".webp": "image/webp",
144
- ".svg": "image/svg+xml",
145
- ".pdf": "application/pdf",
146
- ".txt": "text/plain",
147
- ".xml": "application/xml",
148
- ".zip": "application/zip",
149
- ".mp3": "audio/mpeg",
150
- ".mp4": "video/mp4",
151
- ".webm": "video/webm",
152
- ".doc": "application/msword",
153
- ".docx":
154
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
155
- ".xls": "application/vnd.ms-excel",
156
- ".xlsx":
157
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
158
- ".ppt": "application/vnd.ms-powerpoint",
159
- ".pptx":
160
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
161
- };
162
-
163
- return mimeTypes[ext] || "application/octet-stream";
164
- }
165
-
166
- /**
167
- * 解析Range头
168
- * @param {string} rangeHeader - Range头值
169
- * @param {number} size - 文件大小
170
- * @returns {object|null} 范围对象或null
171
- */
172
- parseRange(rangeHeader, size) {
173
- const matches = rangeHeader.match(/bytes=(\d+)-(\d*)/);
174
-
175
- if (!matches) return null;
176
-
177
- const start = parseInt(matches[1], 10);
178
- const end = matches[2] ? parseInt(matches[2], 10) : size - 1;
179
-
180
- // 验证范围
181
- if (
182
- isNaN(start) ||
183
- isNaN(end) ||
184
- start > end ||
185
- start >= size ||
186
- end >= size
187
- ) {
188
- return null;
189
- }
190
-
191
- return { start, end };
192
- }
193
- }
194
-
195
- module.exports = GetObject;
@@ -1,72 +0,0 @@
1
- const Responses = require("../responses");
2
-
3
- /**
4
- * 处理S3 HeadBucket请求
5
- * 检查存储桶是否存在且用户是否有权限访问
6
- */
7
- class HeadBucket {
8
- constructor(server) {
9
- this.server = server;
10
- this.handle = this.handle.bind(this);
11
- }
12
-
13
- /**
14
- * 处理HeadBucket请求
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
- res.status(404).end();
25
- return;
26
- }
27
-
28
- // 检查存储桶是否存在
29
- const bucketExists = await this.server.driver.bucketExists(bucket);
30
-
31
- if (!bucketExists) {
32
- res.status(404).end();
33
- return;
34
- }
35
-
36
- // 检查是否有权限访问存储桶
37
- const hasAccess = await this.server.driver.hasBucketAccess(bucket);
38
-
39
- if (!hasAccess) {
40
- res.status(403).end();
41
- return;
42
- }
43
-
44
- // 设置响应头
45
- res.set("Date", new Date().toUTCString());
46
- res.set("x-amz-bucket-region", this.server.config.region || "default");
47
-
48
- // 返回成功响应
49
- res.status(200).end();
50
- } catch (error) {
51
- console.error("Error in HeadBucket:", error);
52
- res.status(500).end();
53
- }
54
- }
55
-
56
- /**
57
- * 从请求参数中提取存储桶和键
58
- * @param {object} req - Express请求对象
59
- * @returns {object} 包含存储桶和键的对象
60
- */
61
- extractBucketAndKey(req) {
62
- const url = new URL(req.url, `http://${req.headers.host}`);
63
- const pathParts = url.pathname.split("/").filter(Boolean);
64
-
65
- return {
66
- bucket: pathParts[0],
67
- key: pathParts.slice(1).join("/"),
68
- };
69
- }
70
- }
71
-
72
- module.exports = HeadBucket;
@@ -1,134 +0,0 @@
1
- const Responses = require("../responses");
2
- const path = require("path");
3
- const crypto = require("crypto");
4
-
5
- /**
6
- * 处理S3 HeadObject请求
7
- * 获取对象元数据,不返回对象内容
8
- */
9
- class HeadObject {
10
- constructor(server) {
11
- this.server = server;
12
- this.handle = this.handle.bind(this);
13
- }
14
-
15
- /**
16
- * 处理HeadObject请求
17
- * @param {object} req - Express请求对象
18
- * @param {object} res - Express响应对象
19
- */
20
- async handle(req, res) {
21
- try {
22
- // 获取存储桶和键
23
- const { bucket, key } = this.extractBucketAndKey(req);
24
-
25
- if (!bucket) {
26
- res.status(404).end();
27
- return;
28
- }
29
-
30
- if (!key) {
31
- res.status(400).end();
32
- return;
33
- }
34
-
35
- // 构建对象路径
36
- const objectPath = `/${bucket}/${key}`;
37
-
38
- // 获取对象信息
39
- const stats = await this.server.driver.stat(objectPath);
40
-
41
- if (!stats) {
42
- res.status(404).end();
43
- return;
44
- }
45
-
46
- if (stats.isDirectory) {
47
- res.status(400).end();
48
- return;
49
- }
50
-
51
- // 设置响应头
52
- res.set("Content-Type", this.getContentType(key));
53
- res.set("Content-Length", stats.size.toString());
54
- res.set("Last-Modified", new Date(stats.mtime).toUTCString());
55
- res.set("ETag", `"${stats.uuid || crypto.randomUUID()}"`);
56
- res.set("Accept-Ranges", "bytes");
57
- res.set("Date", new Date().toUTCString());
58
-
59
- // 添加S3特定的元数据头
60
- const metadata = await this.server.driver.getObjectMetadata(objectPath);
61
- if (metadata) {
62
- Object.entries(metadata).forEach(([key, value]) => {
63
- if (key.startsWith("x-amz-meta-")) {
64
- res.set(key, value);
65
- }
66
- });
67
- }
68
-
69
- // 返回空响应体
70
- res.status(200).end();
71
- } catch (error) {
72
- console.error("Error in HeadObject:", error);
73
- res.status(500).end();
74
- }
75
- }
76
-
77
- /**
78
- * 从请求参数中提取存储桶和键
79
- * @param {object} req - Express请求对象
80
- * @returns {object} 包含存储桶和键的对象
81
- */
82
- extractBucketAndKey(req) {
83
- const url = new URL(req.url, `http://${req.headers.host}`);
84
- const pathParts = url.pathname.split("/").filter(Boolean);
85
-
86
- return {
87
- bucket: pathParts[0],
88
- key: pathParts.slice(1).join("/"),
89
- };
90
- }
91
-
92
- /**
93
- * 根据文件扩展名获取内容类型
94
- * @param {string} filename - 文件名
95
- * @returns {string} 内容类型
96
- */
97
- getContentType(filename) {
98
- const ext = path.extname(filename).toLowerCase();
99
-
100
- const mimeTypes = {
101
- ".html": "text/html",
102
- ".htm": "text/html",
103
- ".css": "text/css",
104
- ".js": "application/javascript",
105
- ".json": "application/json",
106
- ".png": "image/png",
107
- ".jpg": "image/jpeg",
108
- ".jpeg": "image/jpeg",
109
- ".gif": "image/gif",
110
- ".webp": "image/webp",
111
- ".svg": "image/svg+xml",
112
- ".pdf": "application/pdf",
113
- ".txt": "text/plain",
114
- ".xml": "application/xml",
115
- ".zip": "application/zip",
116
- ".mp3": "audio/mpeg",
117
- ".mp4": "video/mp4",
118
- ".webm": "video/webm",
119
- ".doc": "application/msword",
120
- ".docx":
121
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
122
- ".xls": "application/vnd.ms-excel",
123
- ".xlsx":
124
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
125
- ".ppt": "application/vnd.ms-powerpoint",
126
- ".pptx":
127
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
128
- };
129
-
130
- return mimeTypes[ext] || "application/octet-stream";
131
- }
132
- }
133
-
134
- module.exports = HeadObject;
@@ -1,38 +0,0 @@
1
- const Responses = require("../responses");
2
-
3
- /**
4
- * 处理S3 ListBuckets请求
5
- * 列出所有可用的存储桶
6
- */
7
- class ListBuckets {
8
- constructor(server) {
9
- this.server = server;
10
- this.handle = this.handle.bind(this);
11
- }
12
-
13
- /**
14
- * 处理ListBuckets请求
15
- * @param {object} req - Express请求对象
16
- * @param {object} res - Express响应对象
17
- */
18
- async handle(req, res) {
19
- try {
20
- // 从驱动获取存储桶列表
21
- const buckets = await this.server.driver.listBuckets();
22
-
23
- // 获取所有者信息
24
- const owner = {
25
- id: this.server.config.accessKeyId || "anonymous",
26
- displayName: this.server.config.displayName || "S3 User",
27
- };
28
-
29
- // 返回存储桶列表
30
- await Responses.listBuckets(res, buckets, owner);
31
- } catch (error) {
32
- console.error("Error in ListBuckets:", error);
33
- await Responses.error(res, 500, "InternalError", "服务器内部错误");
34
- }
35
- }
36
- }
37
-
38
- module.exports = ListBuckets;