Replace Upload class with PutObjectCommand for RustFS compatibility

The @aws-sdk/lib-storage Upload class internally uses
CreateMultipartUploadCommand for files over the part size threshold,
which returns non-standard XML from RustFS causing UnknownError.
PutObjectCommand does a simple single PUT request that RustFS handles
correctly. Fixed in both /api/upload and /api/link/:id/upload endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-04-07 00:52:54 -04:00
parent d5192e8847
commit 44c22bd95e

View file

@ -535,14 +535,14 @@ app.post("/api/upload", requireAuth, upload.array("files", 50), async (req, res)
const key = prefix ? `${prefix.replace(/[-\/]+$/, "")}--${file.originalname}` : file.originalname;
const contentType = getMimeType(file.originalname, file.mimetype);
console.log(`Uploading: ${key} (${file.size} bytes, ${contentType})`);
const uploadPromise = new Upload({
client: s3Client,
params: { Bucket: bucket, Key: key, Body: fs.createReadStream(file.path), ContentType: contentType },
queueSize: 4,
partSize: 10 * 1024 * 1024,
leavePartsOnError: false,
}).done();
const result = await withTimeout(uploadPromise, UPLOAD_TIMEOUT_MS, key);
// Use PutObjectCommand (single PUT) — compatible with RustFS/MinIO/generic S3.
// The @aws-sdk/lib-storage Upload class uses CreateMultipartUpload internally
// which returns non-standard XML from RustFS and causes UnknownError.
const fileBuffer = fs.readFileSync(file.path);
const putPromise = s3Client.send(new PutObjectCommand({
Bucket: bucket, Key: key, Body: fileBuffer, ContentType: contentType,
}));
const result = await withTimeout(putPromise, UPLOAD_TIMEOUT_MS, key);
if (result?.assumed) console.log(`Assumed success (timeout): ${key}`);
else console.log(`Confirmed success: ${key}`);
try { fs.unlinkSync(file.path); } catch (_) {}
@ -841,12 +841,11 @@ app.post("/api/sharelinks/:token/upload", upload.array("files", 50), async (req,
for (const file of req.files) {
const key = prefix ? `${prefix.replace(/[-\/]+$/, "")}--${file.originalname}` : file.originalname;
const contentType = getMimeType(file.originalname, file.mimetype);
const uploadPromise = new Upload({
client: s3Client,
params: { Bucket: bucket, Key: key, Body: fs.createReadStream(file.path), ContentType: contentType },
queueSize: 4, partSize: 10 * 1024 * 1024, leavePartsOnError: false,
}).done();
await withTimeout(uploadPromise, UPLOAD_TIMEOUT_MS, key);
const fileBuffer = fs.readFileSync(file.path);
const putPromise = s3Client.send(new PutObjectCommand({
Bucket: bucket, Key: key, Body: fileBuffer, ContentType: contentType,
}));
await withTimeout(putPromise, UPLOAD_TIMEOUT_MS, key);
try { fs.unlinkSync(file.path); } catch (_) {}
results.push({ originalName: file.originalname, key, size: file.size });
}