@@ -2,7 +2,7 @@ import type { Asset, FontAsset, ImageAsset } from "@webstudio-is/sdk";
2
2
import { nanoid } from "nanoid" ;
3
3
import type { UploadingFileData } from "~/shared/nano-states" ;
4
4
5
- const extensionToMime = new Map ( [
5
+ const imageExtensionToMime = new Map ( [
6
6
[ ".gif" , "image/gif" ] ,
7
7
[ ".ico" , "image/x-icon" ] ,
8
8
[ ".jpeg" , "image/jpeg" ] ,
@@ -12,42 +12,119 @@ const extensionToMime = new Map([
12
12
[ ".webp" , "image/webp" ] ,
13
13
] as const ) ;
14
14
15
- const extensions = [ ...extensionToMime . keys ( ) ] ;
15
+ const imageExtensions = [ ...imageExtensionToMime . keys ( ) ] ;
16
16
17
- export const imageMimeTypes = [ ...extensionToMime . values ( ) ] ;
17
+ export const imageMimeTypes = [ ...imageExtensionToMime . values ( ) ] ;
18
18
19
- export const getImageNameAndType = ( fileName : string ) => {
20
- const extension = extensions . find ( ( ext ) => fileName . endsWith ( ext ) ) ;
19
+ export type ImageMimeType = ( typeof imageMimeTypes ) [ number ] ;
20
+ export type ImageExtension = ( typeof imageExtensions ) [ number ] ;
21
21
22
- if ( extension == null ) {
23
- return ;
22
+ export function getImageExtensionForMimeType (
23
+ mimeType : ImageMimeType
24
+ ) : ImageExtension ;
25
+
26
+ export function getImageExtensionForMimeType (
27
+ mimeType : string
28
+ ) : ImageExtension | undefined ;
29
+
30
+ export function getImageExtensionForMimeType ( mimeType : string ) {
31
+ const index = imageMimeTypes . indexOf ( mimeType as any ) ;
32
+ return index > - 1 ? imageExtensions [ index ] : undefined ;
33
+ }
34
+
35
+ export function getImageNameAndType (
36
+ url : string | URL ,
37
+ defaultExtension : ( typeof imageExtensions ) [ number ]
38
+ ) : [ fileName : string , mimeType : string ] ;
39
+
40
+ export function getImageNameAndType (
41
+ url : string | URL ,
42
+ defaultExtension ?: ( typeof imageExtensions ) [ number ]
43
+ ) : [ fileName : string | undefined , mimeType : string | undefined ] ;
44
+
45
+ export function getImageNameAndType (
46
+ url : string | URL ,
47
+ defaultExtension ?: ( typeof imageExtensions ) [ number ]
48
+ ) : [ fileName : string | undefined , mimeType : string | undefined ] {
49
+ let extension : ( typeof imageExtensions ) [ number ] | undefined ;
50
+
51
+ if ( typeof url === "string" ) {
52
+ extension =
53
+ imageExtensions . find ( ( ext ) => url . endsWith ( ext ) ) ?? defaultExtension ;
54
+
55
+ return extension
56
+ ? [ url , imageExtensionToMime . get ( extension ) ]
57
+ : [ undefined , undefined ] ;
24
58
}
25
59
26
- return [ extensionToMime . get ( extension ) ! , fileName ] as const ;
27
- } ;
60
+ const basename = url . pathname . split ( "/" ) . at ( - 1 ) ?? "" ;
61
+ const contentDispositionKey = / \b c o n t e n t - d i s p o s i t i o n \b / i;
62
+ const contentTypeKey = / \b c o n t e n t - t y p e \b / i;
63
+
64
+ let fileName : string | undefined ;
65
+ let mimeType : string | undefined ;
66
+ extension = imageExtensions . find ( ( ext ) => {
67
+ let foundInSearchParams = false ;
68
+
69
+ // Check every search param in case a filename and/or mime type is specified.
70
+ for ( const key of url . searchParams . keys ( ) ) {
71
+ const value = url . searchParams . get ( key ) ! ;
72
+ if ( ! fileName && contentDispositionKey . test ( key ) ) {
73
+ const fileNameMatch = value . match ( / \b f i l e n a m e = (?: " ( [ ^ " ] + ) " | ( [ ^ ; ] + ) ) / i) ;
74
+ if ( fileNameMatch ) {
75
+ fileName = fileNameMatch [ 1 ] ?? fileNameMatch [ 2 ] ?? "" ;
76
+ }
77
+ } else if ( ! mimeType && contentTypeKey . test ( key ) ) {
78
+ const mimeTypeMatch = value . match ( / \b ( i m a g e \/ [ \w - + ] + ) / i) ;
79
+ if ( mimeTypeMatch ) {
80
+ mimeType = mimeTypeMatch [ 1 ] ;
81
+ }
82
+ } else if ( ! foundInSearchParams && value . endsWith ( ext ) ) {
83
+ foundInSearchParams = true ;
84
+ }
85
+ }
86
+
87
+ if ( basename . endsWith ( ext ) ) {
88
+ if ( ! fileName ?. endsWith ( ext ) ) {
89
+ fileName = basename ;
90
+ }
91
+ return true ;
92
+ }
93
+
94
+ return Boolean (
95
+ foundInSearchParams ||
96
+ fileName ?. endsWith ( ext ) ||
97
+ mimeType === imageExtensionToMime . get ( ext )
98
+ ) ;
99
+ } ) ;
28
100
29
- const extractImageNameAndMimeTypeFromUrl = ( url : URL ) => {
30
- const nameFromPath = url . pathname
31
- . split ( "/" )
32
- . map ( getImageNameAndType )
33
- . filter ( Boolean ) [ 0 ] ;
101
+ extension ??= defaultExtension ;
34
102
35
- if ( nameFromPath != null ) {
36
- return nameFromPath ;
103
+ // Trust the extension over the mime type.
104
+ const impliedMimeType = extension && imageExtensionToMime . get ( extension ) ;
105
+ if ( impliedMimeType ) {
106
+ mimeType = impliedMimeType ;
37
107
}
38
108
39
- const nameFromSearchParams = [ ...url . searchParams . values ( ) ]
40
- . map ( getImageNameAndType )
41
- . filter ( Boolean ) [ 0 ] ;
109
+ return mimeType
110
+ ? [ fileName ?? `${ nanoid ( ) } .${ extension } ` , mimeType ]
111
+ : [ undefined , undefined ] ;
112
+ }
42
113
43
- if ( nameFromSearchParams != null ) {
44
- return nameFromSearchParams ;
114
+ export const getImageName = ( file : File | URL ) => {
115
+ if ( file instanceof File ) {
116
+ return file . name ;
45
117
}
46
118
47
- // Any image format is suitable
48
- const FALLBACK_URL_TYPE = "image/png" ;
119
+ return getImageNameAndType ( file ) [ 0 ] ;
120
+ } ;
121
+
122
+ export const getImageType = ( file : File | URL | string ) => {
123
+ if ( file instanceof File ) {
124
+ return file . type ;
125
+ }
49
126
50
- return [ FALLBACK_URL_TYPE , ` ${ nanoid ( ) } .png` ] as const ;
127
+ return getImageNameAndType ( file ) [ 1 ] ;
51
128
} ;
52
129
53
130
const bufferToHex = ( buffer : ArrayBuffer ) => {
@@ -78,28 +155,13 @@ export const getSha256HashOfFile = async (file: File) => {
78
155
return bufferToHex ( hashBuffer ) ;
79
156
} ;
80
157
81
- export const getMimeType = ( file : File | URL ) => {
82
- if ( file instanceof File ) {
83
- return file . type ;
84
- }
85
-
86
- return extractImageNameAndMimeTypeFromUrl ( file ) [ 0 ] ;
87
- } ;
88
-
89
- export const getFileName = ( file : File | URL ) => {
90
- if ( file instanceof File ) {
91
- return file . name ;
92
- }
93
-
94
- return extractImageNameAndMimeTypeFromUrl ( file ) [ 1 ] ;
95
- } ;
96
-
97
158
export const uploadingFileDataToAsset = (
98
159
fileData : UploadingFileData
99
160
) : Asset => {
100
- const mimeType = getMimeType (
101
- fileData . source === "file" ? fileData . file : new URL ( fileData . url )
102
- ) ;
161
+ const mimeType =
162
+ getImageType (
163
+ fileData . source === "file" ? fileData . file : new URL ( fileData . url )
164
+ ) ?? "image/png" ;
103
165
const format = mimeType . split ( "/" ) [ 1 ] ;
104
166
105
167
if ( mimeType . startsWith ( "image/" ) ) {
0 commit comments