From 3f28489fb856e6e07a16290f3ea1e1261572b527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:07:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(backend):=20multipart=20api=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E7=B5=82=E4=BA=86=E6=99=82=E3=81=AB=E4=B8=80?= =?UTF-8?q?=E6=99=82=E3=83=87=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=81=8C=E6=B6=88=E3=81=88=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#17750)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): multipart apiのエラー時に一時ディレクトリが消えない問題を修正 * fix * fix * Update Changelog --- CHANGELOG.md | 1 + .../backend/src/server/api/ApiCallService.ts | 83 ++++++++++--------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ff08304d..080e9bec0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ - Fix: ハッシュタグに関連するデータを更新する際のエラーハンドリングを修正 - Fix: Sentry 使用環境下にて、Misskey が発行した SQL クエリが span に含まれない問題を修正 - Fix: Sentry 使用環境下にて、外部送信リクエストへ `sentry-trace` / `baggage` ヘッダーが既定で付与されないように +- Fix: ファイルをアップロードするAPIにて、処理終了後に一時ファイルが削除されないことがある問題を修正 ## 2026.6.0 diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 685c8a8ab3..43cba86e3d 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -199,51 +199,54 @@ export class ApiCallService implements OnApplicationShutdown { reply.send(); return; } - const [path, cleanup] = await createTemp(); - await stream.pipeline(multipartData.file, fs.createWriteStream(path)); - // ファイルサイズが制限を超えていた場合 - // なお truncated はストリームを読み切ってからでないと機能しないため、stream.pipeline より後にある必要がある - if (multipartData.file.truncated) { - cleanup(); - reply.code(413); - reply.send(); - return; - } + try { + await stream.pipeline(multipartData.file, fs.createWriteStream(path)); - const fields = {} as Record; - for (const [k, v] of Object.entries(multipartData.fields)) { - fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined; - } - - // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) - const token = request.headers.authorization?.startsWith('Bearer ') - ? request.headers.authorization.slice(7) - : fields['i']; - if (token != null && typeof token !== 'string') { - reply.code(400); - return; - } - - return await this.telemetryService.startSpan('API: ' + endpoint.name, () => this.authenticateService.authenticate(token).then(([user, app]) => { - const call = this.call(endpoint, user, app, fields, { - name: multipartData.filename, - path: path, - }, request).then((res) => { - this.send(reply, res); - }).catch((err: ApiError) => { - this.#sendApiError(reply, err); - }); - - if (user) { - this.logIp(request, user); + // ファイルサイズが制限を超えていた場合 + // なお truncated はストリームを読み切ってからでないと機能しないため、stream.pipeline より後にある必要がある + if (multipartData.file.truncated) { + reply.code(413); + reply.send(); + return; } - return call; - }).catch(err => { - this.#sendAuthenticationError(reply, err); - })); + const fields = {} as Record; + for (const [k, v] of Object.entries(multipartData.fields)) { + fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined; + } + + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : fields['i']; + if (token != null && typeof token !== 'string') { + reply.code(400); + return; + } + + return await this.telemetryService.startSpan('API: ' + endpoint.name, () => this.authenticateService.authenticate(token).then(([user, app]) => { + const call = this.call(endpoint, user, app, fields, { + name: multipartData.filename, + path: path, + }, request).then((res) => { + this.send(reply, res); + }).catch((err: ApiError) => { + this.#sendApiError(reply, err); + }); + + if (user) { + this.logIp(request, user); + } + + return call; + }).catch(err => { + this.#sendAuthenticationError(reply, err); + })); + } finally { + cleanup(); + } } @bindThis