-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathdecodeUtils.ts
More file actions
154 lines (137 loc) · 4.69 KB
/
decodeUtils.ts
File metadata and controls
154 lines (137 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import {Nullable} from "../../typings/common";
import {
Decoder,
DecoderOptions,
} from "../../typings/decoders";
import {
FILE_TYPE_DEFINITIONS,
FileTypeDef,
FileTypeInfo,
} from "../../typings/file";
import {MAX_V8_STRING_LENGTH} from "../../typings/js";
import {getFileFullExtension} from "../../utils/file";
import {formatSizeInBytes} from "../../utils/units";
/**
* Attempts to create a decoder based on the file extension.
*
* @param fileName
* @param fileData
* @param decoderOptions
* @return The decoder, matching extension, and file type definition if successful, null
* otherwise.
*/
const tryCreateDecoderByExtension = async (
fileName: string,
fileData: Uint8Array,
decoderOptions: DecoderOptions
): Promise<Nullable<{
decoder: Decoder; matchingExtension: string; fileTypeDef: FileTypeDef;
}>> => {
for (const entry of FILE_TYPE_DEFINITIONS) {
const matchingExtension = entry.extensions.find((ext) => fileName.endsWith(ext));
if ("undefined" === typeof matchingExtension) {
continue;
}
try {
return {
decoder: await entry.DecoderFactory.create(fileData, decoderOptions),
matchingExtension: matchingExtension,
fileTypeDef: entry,
};
} catch (e) {
console.warn(`File extension matches ${entry.name}, but decoder creation failed.`, e);
}
}
return null;
};
/**
* Attempts to create a decoder based on the file's magic number (signature).
*
* @param fileData
* @param decoderOptions
* @return The decoder and file type definition if successful, null otherwise.
*/
const tryCreateDecoderBySignature = async (
fileData: Uint8Array,
decoderOptions: DecoderOptions
): Promise<Nullable<{decoder: Decoder; fileTypeDef: FileTypeDef}>> => {
for (const entry of FILE_TYPE_DEFINITIONS) {
if (0 === entry.signature.length || fileData.length < entry.signature.length) {
continue;
}
// Check if the file starts with the magic number.
const isSignatureMatching = fileData
.slice(0, entry.signature.length)
.every((byte, idx) => byte === entry.signature[idx]);
if (isSignatureMatching) {
try {
return {
decoder: await entry.DecoderFactory.create(fileData, decoderOptions),
fileTypeDef: entry,
};
} catch (e) {
console.warn(`Magic number matches ${entry.name}, but decoder creation failed:`, e);
}
}
}
return null;
};
/**
* Resolves the appropriate decoder and file type information based on the file name and data.
*
* @param fileName
* @param fileData
* @param decoderOptions
* @return The constructed decoder and file type information.
* @throws {Error} if the file is too large, or no decoder supports the file.
*/
const resolveDecoderAndFileType = async (
fileName: string,
fileData: Uint8Array,
decoderOptions: DecoderOptions
): Promise<{
decoder: Decoder;
fileTypeInfo: FileTypeInfo;
}> => {
if (fileData.length > MAX_V8_STRING_LENGTH) {
throw new Error(`Cannot handle files larger than ${
formatSizeInBytes(MAX_V8_STRING_LENGTH)
} due to a limitation in Chromium-based browsers.`);
}
let fileExtension = getFileFullExtension(fileName);
let fileTypeDef = null;
let decoder = null;
// Try to create a decoder based on the file extension.
if (0 < fileExtension.length) {
const extensionResult = await tryCreateDecoderByExtension(
fileName,
fileData,
decoderOptions,
);
if (null !== extensionResult) {
({decoder, matchingExtension: fileExtension, fileTypeDef} = extensionResult);
}
}
// If no decoder was found by extension, try to create one based on the file's magic number.
if (null === decoder) {
console.warn(`No valid decoder was found for file extension "${fileExtension}". ` +
"Trying to match by signature.");
const signatureResult = await tryCreateDecoderBySignature(fileData, decoderOptions);
if (null !== signatureResult) {
({decoder, fileTypeDef} = signatureResult);
}
}
if (null === decoder || null === fileTypeDef) {
throw new Error(`No decoder supports the file "${fileName}".`);
}
return {
decoder: decoder,
fileTypeInfo: {
extension: fileExtension,
isStructured: fileTypeDef.checkIsStructured(decoder),
name: fileTypeDef.name,
signature: fileTypeDef.signature,
},
};
};
export {resolveDecoderAndFileType};