summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authortamaina <tamaina@hotmail.co.jp>2023-03-04 16:51:07 +0900
committerGitHub <noreply@github.com>2023-03-04 16:51:07 +0900
commit2d551a8598de12210ddb7f708561e51867ce3f10 (patch)
tree59fea9e7a025693814b8e5e596dc2c43e6464034 /packages/backend/src/core
parentfix(server): DriveFile related N+1 query when call note packMany (again) (#10... (diff)
downloadmisskey-2d551a8598de12210ddb7f708561e51867ce3f10.tar.gz
misskey-2d551a8598de12210ddb7f708561e51867ce3f10.tar.bz2
misskey-2d551a8598de12210ddb7f708561e51867ce3f10.zip
enhance(server): downloadUrlでContent-Dispositionからファイル名を取得 (#10150)
* enhance(server): downloadUrlでContent-Dispositionからファイル名を取得 Resolve #10036 Resolve #4750 * untitled * オブジェクトストレージのContent-Dispositionのファイル名の拡張子をContent-Typeに添ったものにする * :v: * tiff * fix filename * add test * /files/でもContent-Disposition * comment * fix test
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/DownloadService.ts20
-rw-r--r--packages/backend/src/core/DriveService.ts44
2 files changed, 44 insertions, 20 deletions
diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts
index 852c1f32e3..bd999c67da 100644
--- a/packages/backend/src/core/DownloadService.ts
+++ b/packages/backend/src/core/DownloadService.ts
@@ -6,6 +6,7 @@ import IPCIDR from 'ip-cidr';
import PrivateIp from 'private-ip';
import chalk from 'chalk';
import got, * as Got from 'got';
+import { parse } from 'content-disposition';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
@@ -32,13 +33,18 @@ export class DownloadService {
}
@bindThis
- public async downloadUrl(url: string, path: string): Promise<void> {
+ public async downloadUrl(url: string, path: string): Promise<{
+ filename: string;
+ }> {
this.logger.info(`Downloading ${chalk.cyan(url)} to ${chalk.cyanBright(path)} ...`);
const timeout = 30 * 1000;
const operationTimeout = 60 * 1000;
const maxSize = this.config.maxFileSize ?? 262144000;
+ const urlObj = new URL(url);
+ let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
+
const req = got.stream(url, {
headers: {
'User-Agent': this.config.userAgent,
@@ -77,6 +83,14 @@ export class DownloadService {
req.destroy();
}
}
+
+ const contentDisposition = res.headers['content-disposition'];
+ if (contentDisposition != null) {
+ const parsed = parse(contentDisposition);
+ if (parsed.parameters.filename) {
+ filename = parsed.parameters.filename;
+ }
+ }
}).on('downloadProgress', (progress: Got.Progress) => {
if (progress.transferred > maxSize) {
this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
@@ -95,6 +109,10 @@ export class DownloadService {
}
this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
+
+ return {
+ filename,
+ };
}
@bindThis
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index b15c967c85..f4a06faebb 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -34,6 +34,7 @@ import { FileInfoService } from '@/core/FileInfoService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type S3 from 'aws-sdk/clients/s3.js';
+import { correctFilename } from '@/misc/correct-filename.js';
type AddFileArgs = {
/** User who wish to add file */
@@ -168,7 +169,7 @@ export class DriveService {
//#region Uploads
this.registerLogger.info(`uploading original: ${key}`);
const uploads = [
- this.upload(key, fs.createReadStream(path), type, name),
+ this.upload(key, fs.createReadStream(path), type, ext, name),
];
if (alts.webpublic) {
@@ -176,7 +177,7 @@ export class DriveService {
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
- uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name));
+ uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, alts.webpublic.ext, name));
}
if (alts.thumbnail) {
@@ -184,7 +185,7 @@ export class DriveService {
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
- uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type));
+ uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext));
}
await Promise.all(uploads);
@@ -360,7 +361,7 @@ export class DriveService {
* Upload to ObjectStorage
*/
@bindThis
- private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
+ private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, ext?: string | null, filename?: string) {
if (type === 'image/apng') type = 'image/png';
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
@@ -374,7 +375,12 @@ export class DriveService {
CacheControl: 'max-age=31536000, immutable',
} as S3.PutObjectRequest;
- if (filename) params.ContentDisposition = contentDisposition('inline', filename);
+ if (filename) params.ContentDisposition = contentDisposition(
+ 'inline',
+ // 拡張子からContent-Typeを設定してそうな挙動を示すオブジェクトストレージ (upcloud?) も存在するので、
+ // 許可されているファイル形式でしか拡張子をつけない
+ ext ? correctFilename(filename, ext) : filename,
+ );
if (meta.objectStorageSetPublicRead) params.ACL = 'public-read';
const s3 = this.s3Service.getS3(meta);
@@ -466,7 +472,12 @@ export class DriveService {
//}
// detect name
- const detectedName = name ?? (info.type.ext ? `untitled.${info.type.ext}` : 'untitled');
+ const detectedName = correctFilename(
+ // DriveFile.nameは256文字, validateFileNameは200文字制限であるため、
+ // extを付加してデータベースの文字数制限に当たることはまずない
+ (name && this.driveFileEntityService.validateFileName(name)) ? name : 'untitled',
+ info.type.ext
+ );
if (user && !force) {
// Check if there is a file with the same hash
@@ -736,24 +747,19 @@ export class DriveService {
requestIp = null,
requestHeaders = null,
}: UploadFromUrlArgs): Promise<DriveFile> {
- let name = new URL(url).pathname.split('/').pop() ?? null;
- if (name == null || !this.driveFileEntityService.validateFileName(name)) {
- name = null;
- }
-
- // If the comment is same as the name, skip comment
- // (image.name is passed in when receiving attachment)
- if (comment !== null && name === comment) {
- comment = null;
- }
-
// Create temp file
const [path, cleanup] = await createTemp();
try {
// write content at URL to temp file
- await this.downloadService.downloadUrl(url, path);
-
+ const { filename: name } = await this.downloadService.downloadUrl(url, path);
+
+ // If the comment is same as the name, skip comment
+ // (image.name is passed in when receiving attachment)
+ if (comment !== null && name === comment) {
+ comment = null;
+ }
+
const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders });
this.downloaderLogger.succ(`Got: ${driveFile.id}`);
return driveFile!;