enhance(server): downloadUrlでContent-Dispositionからファイル名を取得 (#10150)

* enhance(server): downloadUrlでContent-Dispositionからファイル名を取得
Resolve #10036
Resolve #4750

* untitled

* オブジェクトストレージのContent-Dispositionのファイル名の拡張子をContent-Typeに添ったものにする

* ✌️

* tiff

* fix filename

* add test

* /files/でもContent-Disposition

* comment

* fix test
This commit is contained in:
tamaina 2023-03-04 16:51:07 +09:00 committed by GitHub
parent 49f0837729
commit 2d551a8598
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 42 deletions

View file

@ -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!;