fix(server): file uploads for files with extension only filenames (#30024)

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
Rust 2026-07-21 06:57:28 -05:00 committed by GitHub
parent 8365cdd59e
commit 1b4d41324e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 24 additions and 15 deletions

View file

@ -9,15 +9,19 @@ import { ImmichReadStream } from 'src/repositories/storage.repository';
import { isConnectionAborted } from 'src/utils/misc';
export function getFileNameWithoutExtension(path: string): string {
return basename(path, extname(path));
return basename(path, getFilenameExtension(path));
}
export function getFilenameExtension(path: string): string {
return extname(path);
export function getFilenameExtension(path: string) {
const extension = extname(path);
if (!extension && path.startsWith('.') && !path.includes('.', 1)) {
return path;
}
return extension;
}
export function getLivePhotoMotionFilename(stillName: string, motionName: string) {
return getFileNameWithoutExtension(stillName) + extname(motionName);
return getFileNameWithoutExtension(stillName) + getFilenameExtension(motionName);
}
export class ImmichFileResponse {

View file

@ -1,5 +1,5 @@
import { extname } from 'node:path';
import { AssetType } from 'src/enum';
import { getFilenameExtension } from 'src/utils/file';
const raw = {
'.3fr': ['image/3fr', 'image/x-hasselblad-3fr'],
@ -132,10 +132,12 @@ const sidecar: Record<string, string[]> = {
const types = { ...image, ...video, ...sidecar };
const isType = (filename: string, record: Record<string, string[]>) =>
Object.hasOwn(record, extname(filename).toLowerCase());
const isType = (filename: string, r: Record<string, string[]>) =>
Object.hasOwn(r, getFilenameExtension(filename).toLowerCase());
const lookup = (filename: string) =>
types[getFilenameExtension(filename).toLowerCase()]?.[0] ?? 'application/octet-stream';
const lookup = (filename: string) => types[extname(filename).toLowerCase()]?.[0] ?? 'application/octet-stream';
const toExtension = (mimeType: string) => {
return (
extensionOverrides[mimeType] || Object.entries(types).find(([, mimeTypes]) => mimeTypes.includes(mimeType))?.[0]
@ -158,7 +160,8 @@ export const mimeTypes = {
isProfile: (filename: string) => isType(filename, profile),
isSidecar: (filename: string) => isType(filename, sidecar),
isVideo: (filename: string) => isType(filename, video),
canBeTransparent: (filename: string) => transparentCapableExtensions.has(extname(filename).toLowerCase()),
canBeTransparent: (filename: string) =>
transparentCapableExtensions.has(getFilenameExtension(filename).toLowerCase()),
isRaw: (filename: string) => isType(filename, raw),
lookup,
/** return an extension (including a leading `.`) for a mime-type */