2018-12-26 23:19:46 +00:00
|
|
|
/**
|
|
|
|
* File signatures and extractor functions
|
|
|
|
*
|
|
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
|
|
* @copyright Crown Copyright 2018
|
|
|
|
* @license Apache-2.0
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
import Stream from "./Stream";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A categorised table of file types, including signatures to identify them and functions
|
|
|
|
* to extract them where possible.
|
|
|
|
*/
|
|
|
|
export const FILE_SIGNATURES = {
|
|
|
|
"Images": [
|
|
|
|
{
|
|
|
|
name: "Joint Photographic Experts Group image",
|
2018-12-27 00:03:41 +00:00
|
|
|
extension: "jpg,jpeg,jpe,thm,mpo",
|
2018-12-26 23:19:46 +00:00
|
|
|
mime: "image/jpeg",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0xff,
|
|
|
|
1: 0xd8,
|
2018-12-27 00:03:41 +00:00
|
|
|
2: 0xff,
|
|
|
|
3: [0xc0, 0xc4, 0xdb, 0xdd, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe7, 0xe8, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xfe]
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
extractor: extractJPEG
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Graphics Interchange Format image",
|
|
|
|
extension: "gif",
|
|
|
|
mime: "image/gif",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
2018-12-27 00:03:41 +00:00
|
|
|
0: 0x47, // GIF
|
2018-12-26 23:19:46 +00:00
|
|
|
1: 0x49,
|
2018-12-27 00:03:41 +00:00
|
|
|
2: 0x46,
|
|
|
|
3: 0x38, // 8
|
|
|
|
4: [0x37, 0x39], // 7|9
|
|
|
|
5: 0x61 // a
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Portable Network Graphics image",
|
|
|
|
extension: "png",
|
|
|
|
mime: "image/png",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x89,
|
2018-12-27 00:03:41 +00:00
|
|
|
1: 0x50, // PNG
|
2018-12-26 23:19:46 +00:00
|
|
|
2: 0x4e,
|
2018-12-27 00:03:41 +00:00
|
|
|
3: 0x47,
|
|
|
|
4: 0x0d,
|
|
|
|
5: 0x0a,
|
|
|
|
6: 0x1a,
|
|
|
|
7: 0x0a
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
2018-12-30 02:21:45 +00:00
|
|
|
extractor: extractPNG
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "WEBP Image",
|
|
|
|
extension: "webp",
|
|
|
|
mime: "image/webp",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
8: 0x57,
|
|
|
|
9: 0x45,
|
|
|
|
10: 0x42,
|
|
|
|
11: 0x50
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
2018-12-27 00:03:41 +00:00
|
|
|
{
|
|
|
|
name: "Camera Image File Format",
|
|
|
|
extension: "crw",
|
|
|
|
mime: "image/x-canon-crw",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
6: 0x48, // HEAPCCDR
|
|
|
|
7: 0x45,
|
|
|
|
8: 0x41,
|
|
|
|
9: 0x50,
|
|
|
|
10: 0x43,
|
|
|
|
11: 0x43,
|
|
|
|
12: 0x44,
|
|
|
|
13: 0x52
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
2018-12-26 23:19:46 +00:00
|
|
|
{ // Place before tiff check
|
|
|
|
name: "Canon CR2 raw image",
|
|
|
|
extension: "cr2",
|
|
|
|
mime: "image/x-canon-cr2",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
0: 0x49,
|
|
|
|
1: 0x49,
|
|
|
|
2: 0x2a,
|
|
|
|
3: 0x0,
|
|
|
|
8: 0x43,
|
|
|
|
9: 0x52
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0x4d,
|
|
|
|
1: 0x4d,
|
|
|
|
2: 0x0,
|
|
|
|
3: 0x2a,
|
|
|
|
8: 0x43,
|
|
|
|
9: 0x52
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Tagged Image File Format image",
|
|
|
|
extension: "tif",
|
|
|
|
mime: "image/tiff",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
0: 0x49,
|
|
|
|
1: 0x49,
|
|
|
|
2: 0x2a,
|
|
|
|
3: 0x0
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0x4d,
|
|
|
|
1: 0x4d,
|
|
|
|
2: 0x0,
|
|
|
|
3: 0x2a
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Bitmap image",
|
|
|
|
extension: "bmp",
|
|
|
|
mime: "image/bmp",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x42,
|
2018-12-27 00:03:41 +00:00
|
|
|
1: 0x4d,
|
|
|
|
7: 0x0,
|
|
|
|
9: 0x0,
|
|
|
|
14: [0x0c, 0x28, 0x38, 0x40, 0x6c, 0x7c],
|
|
|
|
15: 0x0,
|
|
|
|
16: 0x0,
|
|
|
|
17: 0x0
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
2018-12-30 02:21:45 +00:00
|
|
|
extractor: extractBMP
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "JPEG Extended Range image",
|
|
|
|
extension: "jxr",
|
|
|
|
mime: "image/vnd.ms-photo",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x49,
|
|
|
|
1: 0x49,
|
|
|
|
2: 0xbc
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Photoshop image",
|
|
|
|
extension: "psd",
|
|
|
|
mime: "image/vnd.adobe.photoshop",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x38,
|
|
|
|
1: 0x42,
|
|
|
|
2: 0x50,
|
2018-12-27 00:03:41 +00:00
|
|
|
3: 0x53,
|
|
|
|
4: 0x0,
|
|
|
|
5: 0x1,
|
|
|
|
6: 0x0,
|
|
|
|
7: 0x0,
|
|
|
|
8: 0x0,
|
|
|
|
9: 0x0,
|
|
|
|
10: 0x0,
|
|
|
|
11: 0x0
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
2018-12-27 00:03:41 +00:00
|
|
|
{
|
|
|
|
name: "Paint Shop Pro image",
|
|
|
|
extension: "psp",
|
|
|
|
mime: "image/psp",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
0: 0x50, // Paint Shop Pro Im
|
|
|
|
1: 0x61,
|
|
|
|
2: 0x69,
|
|
|
|
3: 0x6e,
|
|
|
|
4: 0x74,
|
|
|
|
5: 0x20,
|
|
|
|
6: 0x53,
|
|
|
|
7: 0x68,
|
|
|
|
8: 0x6f,
|
|
|
|
9: 0x70,
|
|
|
|
10: 0x20,
|
|
|
|
11: 0x50,
|
|
|
|
12: 0x72,
|
|
|
|
13: 0x6f,
|
|
|
|
14: 0x20,
|
|
|
|
15: 0x49,
|
|
|
|
16: 0x6d
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0x7e,
|
|
|
|
1: 0x42,
|
|
|
|
2: 0x4b,
|
|
|
|
3: 0x0
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
2018-12-26 23:19:46 +00:00
|
|
|
{
|
|
|
|
name: "Icon image",
|
|
|
|
extension: "ico",
|
|
|
|
mime: "image/x-icon",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x0,
|
|
|
|
2: 0x1,
|
2018-12-27 00:03:41 +00:00
|
|
|
3: 0x0,
|
|
|
|
4: [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15],
|
|
|
|
5: 0x0,
|
|
|
|
6: [0x10, 0x20, 0x30, 0x40, 0x80],
|
|
|
|
7: [0x10, 0x20, 0x30, 0x40, 0x80],
|
|
|
|
9: 0x00,
|
|
|
|
10: [0x0, 0x1]
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"Video": [
|
|
|
|
{ // Place before webm
|
|
|
|
name: "Matroska Multimedia Container",
|
|
|
|
extension: "mkv",
|
|
|
|
mime: "video/x-matroska",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
31: 0x6d,
|
|
|
|
32: 0x61,
|
|
|
|
33: 0x74,
|
|
|
|
34: 0x72,
|
|
|
|
35: 0x6f,
|
|
|
|
36: 0x73,
|
|
|
|
37: 0x6b,
|
|
|
|
38: 0x61
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "WEBM video",
|
|
|
|
extension: "webm",
|
|
|
|
mime: "video/webm",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x1a,
|
|
|
|
1: 0x45,
|
|
|
|
2: 0xdf,
|
|
|
|
3: 0xa3
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "MPEG-4 video",
|
|
|
|
extension: "mp4",
|
|
|
|
mime: "video/mp4",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x0,
|
|
|
|
2: 0x0,
|
|
|
|
3: [0x18, 0x20],
|
|
|
|
4: 0x66,
|
|
|
|
5: 0x74,
|
|
|
|
6: 0x79,
|
|
|
|
7: 0x70
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0x33, // 3gp5
|
|
|
|
1: 0x67,
|
|
|
|
2: 0x70,
|
|
|
|
3: 0x35
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x0,
|
|
|
|
2: 0x0,
|
|
|
|
3: 0x1c,
|
|
|
|
4: 0x66,
|
|
|
|
5: 0x74,
|
|
|
|
6: 0x79,
|
|
|
|
7: 0x70,
|
|
|
|
8: 0x6d,
|
|
|
|
9: 0x70,
|
|
|
|
10: 0x34,
|
|
|
|
11: 0x32,
|
|
|
|
16: 0x6d, // mp41mp42isom
|
|
|
|
17: 0x70,
|
|
|
|
18: 0x34,
|
|
|
|
19: 0x31,
|
|
|
|
20: 0x6d,
|
|
|
|
21: 0x70,
|
|
|
|
22: 0x34,
|
|
|
|
23: 0x32,
|
|
|
|
24: 0x69,
|
|
|
|
25: 0x73,
|
|
|
|
26: 0x6f,
|
|
|
|
27: 0x6d
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "M4V video",
|
|
|
|
extension: "m4v",
|
|
|
|
mime: "video/x-m4v",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x0,
|
|
|
|
2: 0x0,
|
|
|
|
3: 0x1c,
|
|
|
|
4: 0x66,
|
|
|
|
5: 0x74,
|
|
|
|
6: 0x79,
|
|
|
|
7: 0x70,
|
|
|
|
8: 0x4d,
|
|
|
|
9: 0x34,
|
|
|
|
10: 0x56
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Quicktime video",
|
|
|
|
extension: "mov",
|
|
|
|
mime: "video/quicktime",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x0,
|
|
|
|
2: 0x0,
|
|
|
|
3: 0x14,
|
|
|
|
4: 0x66,
|
|
|
|
5: 0x74,
|
|
|
|
6: 0x79,
|
|
|
|
7: 0x70
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Audio Video Interleave",
|
|
|
|
extension: "avi",
|
|
|
|
mime: "video/x-msvideo",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x52,
|
|
|
|
1: 0x49,
|
|
|
|
2: 0x46,
|
|
|
|
3: 0x46,
|
|
|
|
8: 0x41,
|
|
|
|
9: 0x56,
|
|
|
|
10: 0x49
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Windows Media Video",
|
|
|
|
extension: "wmv",
|
|
|
|
mime: "video/x-ms-wmv",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x30,
|
|
|
|
1: 0x26,
|
|
|
|
2: 0xb2,
|
|
|
|
3: 0x75,
|
|
|
|
4: 0x8e,
|
|
|
|
5: 0x66,
|
|
|
|
6: 0xcf,
|
|
|
|
7: 0x11,
|
|
|
|
8: 0xa6,
|
|
|
|
9: 0xd9
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "MPEG video",
|
|
|
|
extension: "mpg",
|
|
|
|
mime: "video/mpeg",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x0,
|
|
|
|
2: 0x1,
|
|
|
|
3: 0xba
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Flash Video",
|
|
|
|
extension: "flv",
|
|
|
|
mime: "video/x-flv",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x46,
|
|
|
|
1: 0x4c,
|
|
|
|
2: 0x56,
|
|
|
|
3: 0x1
|
|
|
|
},
|
2019-01-03 18:40:22 +00:00
|
|
|
extractor: extractFLV
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
"Audio": [
|
|
|
|
{
|
|
|
|
name: "Waveform Audio",
|
|
|
|
extension: "wav",
|
|
|
|
mime: "audio/x-wav",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x52,
|
|
|
|
1: 0x49,
|
|
|
|
2: 0x46,
|
|
|
|
3: 0x46,
|
|
|
|
8: 0x57,
|
|
|
|
9: 0x41,
|
|
|
|
10: 0x56,
|
|
|
|
11: 0x45
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "OGG audio",
|
|
|
|
extension: "ogg",
|
|
|
|
mime: "audio/ogg",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x4f,
|
|
|
|
1: 0x67,
|
|
|
|
2: 0x67,
|
|
|
|
3: 0x53
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Musical Instrument Digital Interface audio",
|
|
|
|
extension: "midi",
|
|
|
|
mime: "audio/midi",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x4d,
|
|
|
|
1: 0x54,
|
|
|
|
2: 0x68,
|
|
|
|
3: 0x64
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "MPEG-3 audio",
|
|
|
|
extension: "mp3",
|
|
|
|
mime: "audio/mpeg",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
0: 0x49,
|
|
|
|
1: 0x44,
|
|
|
|
2: 0x33
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0xff,
|
|
|
|
1: 0xfb
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "MPEG-4 Part 14 audio",
|
|
|
|
extension: "m4a",
|
|
|
|
mime: "audio/m4a",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
4: 0x66,
|
|
|
|
5: 0x74,
|
|
|
|
6: 0x79,
|
|
|
|
7: 0x70,
|
|
|
|
8: 0x4d,
|
|
|
|
9: 0x34,
|
|
|
|
10: 0x41
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0: 0x4d,
|
|
|
|
1: 0x34,
|
|
|
|
2: 0x41,
|
|
|
|
3: 0x20
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Free Lossless Audio Codec",
|
|
|
|
extension: "flac",
|
|
|
|
mime: "audio/x-flac",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x66,
|
|
|
|
1: 0x4c,
|
|
|
|
2: 0x61,
|
|
|
|
3: 0x43
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Adaptive Multi-Rate audio codec",
|
|
|
|
extension: "amr",
|
|
|
|
mime: "audio/amr",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x23,
|
|
|
|
1: 0x21,
|
|
|
|
2: 0x41,
|
|
|
|
3: 0x4d,
|
|
|
|
4: 0x52,
|
|
|
|
5: 0x0a
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"Documents": [
|
|
|
|
{
|
|
|
|
name: "Portable Document Format",
|
|
|
|
extension: "pdf",
|
|
|
|
mime: "application/pdf",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x25,
|
|
|
|
1: 0x50,
|
|
|
|
2: 0x44,
|
|
|
|
3: 0x46
|
|
|
|
},
|
|
|
|
extractor: extractPDF
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "PostScript",
|
|
|
|
extension: "ps",
|
|
|
|
mime: "application/postscript",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x25,
|
|
|
|
1: 0x21
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Rich Text Format",
|
|
|
|
extension: "rtf",
|
|
|
|
mime: "application/rtf",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x7b,
|
|
|
|
1: 0x5c,
|
|
|
|
2: 0x72,
|
|
|
|
3: 0x74,
|
|
|
|
4: 0x66
|
|
|
|
},
|
2019-01-04 14:57:31 +00:00
|
|
|
extractor: extractRTF
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Microsoft Office documents/OLE2",
|
|
|
|
extension: "ole2,doc,xls,dot,ppt,xla,ppa,pps,pot,msi,sdw,db,vsd,msg",
|
|
|
|
mime: "application/msword,application/vnd.ms-excel,application/vnd.ms-powerpoint",
|
|
|
|
description: "Microsoft Office documents",
|
|
|
|
signature: {
|
|
|
|
0: 0xd0,
|
|
|
|
1: 0xcf,
|
|
|
|
2: 0x11,
|
|
|
|
3: 0xe0,
|
|
|
|
4: 0xa1,
|
|
|
|
5: 0xb1,
|
|
|
|
6: 0x1a,
|
|
|
|
7: 0xe1
|
|
|
|
},
|
|
|
|
extractor: null
|
2018-12-27 00:03:41 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Microsoft Office 2007+ documents",
|
|
|
|
extension: "docx,xlsx,pptx",
|
|
|
|
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
38: 0x5f, // _Types].xml
|
|
|
|
39: 0x54,
|
|
|
|
40: 0x79,
|
|
|
|
41: 0x70,
|
|
|
|
42: 0x65,
|
|
|
|
43: 0x73,
|
|
|
|
44: 0x5d,
|
|
|
|
45: 0x2e,
|
|
|
|
46: 0x78,
|
|
|
|
47: 0x6d,
|
|
|
|
48: 0x6c
|
|
|
|
},
|
2019-01-03 19:01:12 +00:00
|
|
|
extractor: extractZIP
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "EPUB e-book",
|
|
|
|
extension: "epub",
|
|
|
|
mime: "application/epub+zip",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x50,
|
|
|
|
1: 0x4b,
|
|
|
|
2: 0x3,
|
|
|
|
3: 0x4,
|
|
|
|
30: 0x6d, // mimetypeapplication/epub_zip
|
|
|
|
31: 0x69,
|
|
|
|
32: 0x6d,
|
|
|
|
33: 0x65,
|
|
|
|
34: 0x74,
|
|
|
|
35: 0x79,
|
|
|
|
36: 0x70,
|
|
|
|
37: 0x65,
|
|
|
|
38: 0x61,
|
|
|
|
39: 0x70,
|
|
|
|
40: 0x70,
|
|
|
|
41: 0x6c,
|
|
|
|
42: 0x69,
|
|
|
|
43: 0x63,
|
|
|
|
44: 0x61,
|
|
|
|
45: 0x74,
|
|
|
|
46: 0x69,
|
|
|
|
47: 0x6f,
|
|
|
|
48: 0x6e,
|
|
|
|
49: 0x2f,
|
|
|
|
50: 0x65,
|
|
|
|
51: 0x70,
|
|
|
|
52: 0x75,
|
|
|
|
53: 0x62,
|
|
|
|
54: 0x2b,
|
|
|
|
55: 0x7a,
|
|
|
|
56: 0x69,
|
|
|
|
57: 0x70
|
|
|
|
},
|
2019-01-04 18:12:49 +00:00
|
|
|
extractor: extractZIP
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
"Applications": [
|
|
|
|
{
|
|
|
|
name: "Windows Portable Executable",
|
|
|
|
extension: "exe,dll,drv,vxd,sys,ocx,vbx,com,fon,scr",
|
|
|
|
mime: "application/x-msdownload",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x4d,
|
|
|
|
1: 0x5a,
|
|
|
|
3: [0x0, 0x1, 0x2],
|
|
|
|
5: [0x0, 0x1, 0x2]
|
|
|
|
},
|
|
|
|
extractor: extractMZPE
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Executable and Linkable Format file",
|
|
|
|
extension: "elf,bin,axf,o,prx,so",
|
|
|
|
mime: "application/x-executable",
|
|
|
|
description: "Executable and Linkable Format file. No standard file extension.",
|
|
|
|
signature: {
|
|
|
|
0: 0x7f,
|
|
|
|
1: 0x45,
|
|
|
|
2: 0x4c,
|
|
|
|
3: 0x46
|
|
|
|
},
|
2019-01-14 18:55:10 +00:00
|
|
|
extractor: extractELF
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Adobe Flash",
|
|
|
|
extension: "swf",
|
|
|
|
mime: "application/x-shockwave-flash",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: [0x43, 0x46],
|
|
|
|
1: 0x57,
|
|
|
|
2: 0x53
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Java Class",
|
|
|
|
extension: "class",
|
|
|
|
mime: "application/java-vm",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0xca,
|
|
|
|
1: 0xfe,
|
|
|
|
2: 0xba,
|
|
|
|
3: 0xbe
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Dalvik Executable",
|
|
|
|
extension: "dex",
|
|
|
|
mime: "application/octet-stream",
|
|
|
|
description: "Dalvik Executable as used by Android",
|
|
|
|
signature: {
|
|
|
|
0: 0x64,
|
|
|
|
1: 0x65,
|
|
|
|
2: 0x78,
|
|
|
|
3: 0x0a,
|
|
|
|
4: 0x30,
|
|
|
|
5: 0x33,
|
|
|
|
6: 0x35,
|
|
|
|
7: 0x0
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Google Chrome Extension",
|
|
|
|
extension: "crx",
|
|
|
|
mime: "application/crx",
|
|
|
|
description: "Google Chrome extension or packaged app",
|
|
|
|
signature: {
|
|
|
|
0: 0x43,
|
|
|
|
1: 0x72,
|
|
|
|
2: 0x32,
|
|
|
|
3: 0x34
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"Archives": [
|
|
|
|
{
|
|
|
|
name: "PKZIP archive",
|
|
|
|
extension: "zip",
|
|
|
|
mime: "application/zip",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x50,
|
|
|
|
1: 0x4b,
|
|
|
|
2: [0x3, 0x5, 0x7],
|
|
|
|
3: [0x4, 0x6, 0x8]
|
|
|
|
},
|
|
|
|
extractor: extractZIP
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "TAR archive",
|
|
|
|
extension: "tar",
|
|
|
|
mime: "application/x-tar",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
257: 0x75,
|
|
|
|
258: 0x73,
|
|
|
|
259: 0x74,
|
|
|
|
260: 0x61,
|
|
|
|
261: 0x72
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Roshal Archive",
|
|
|
|
extension: "rar",
|
|
|
|
mime: "application/x-rar-compressed",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x52,
|
|
|
|
1: 0x61,
|
|
|
|
2: 0x72,
|
|
|
|
3: 0x21,
|
|
|
|
4: 0x1a,
|
|
|
|
5: 0x7,
|
|
|
|
6: [0x0, 0x1]
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Gzip",
|
|
|
|
extension: "gz",
|
|
|
|
mime: "application/gzip",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x1f,
|
|
|
|
1: 0x8b,
|
|
|
|
2: 0x8
|
|
|
|
},
|
2019-01-04 18:12:49 +00:00
|
|
|
extractor: extractGZIP
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Bzip2",
|
|
|
|
extension: "bz2",
|
|
|
|
mime: "application/x-bzip2",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x42,
|
|
|
|
1: 0x5a,
|
|
|
|
2: 0x68
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "7zip",
|
|
|
|
extension: "7z",
|
|
|
|
mime: "application/x-7z-compressed",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x37,
|
|
|
|
1: 0x7a,
|
|
|
|
2: 0xbc,
|
|
|
|
3: 0xaf,
|
|
|
|
4: 0x27,
|
|
|
|
5: 0x1c
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Zlib Deflate",
|
|
|
|
extension: "zlib",
|
|
|
|
mime: "application/x-deflate",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x78,
|
|
|
|
1: [0x1, 0x9c, 0xda, 0x5e]
|
|
|
|
},
|
2019-01-11 17:58:25 +00:00
|
|
|
extractor: extractZlib
|
2018-12-26 23:19:46 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "xz compression",
|
|
|
|
extension: "xz",
|
|
|
|
mime: "application/x-xz",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0xfd,
|
|
|
|
1: 0x37,
|
|
|
|
2: 0x7a,
|
|
|
|
3: 0x58,
|
|
|
|
4: 0x5a,
|
|
|
|
5: 0x0
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Tarball",
|
|
|
|
extension: "tar.z",
|
|
|
|
mime: "application/x-gtar",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x1f,
|
|
|
|
1: [0x9d, 0xa0]
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ISO disk image",
|
|
|
|
extension: "iso",
|
|
|
|
mime: "application/octet-stream",
|
|
|
|
description: "ISO 9660 CD/DVD image file",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
0x8001: 0x43,
|
|
|
|
0x8002: 0x44,
|
|
|
|
0x8003: 0x30,
|
|
|
|
0x8004: 0x30,
|
|
|
|
0x8005: 0x31
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0x8801: 0x43,
|
|
|
|
0x8802: 0x44,
|
|
|
|
0x8803: 0x30,
|
|
|
|
0x8804: 0x30,
|
|
|
|
0x8805: 0x31
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0x9001: 0x43,
|
|
|
|
0x9002: 0x44,
|
|
|
|
0x9003: 0x30,
|
|
|
|
0x9004: 0x30,
|
|
|
|
0x9005: 0x31
|
|
|
|
}
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Virtual Machine Disk",
|
|
|
|
extension: "vmdk",
|
|
|
|
mime: "application/vmdk,application/x-virtualbox-vmdk",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x4b,
|
|
|
|
1: 0x44,
|
|
|
|
2: 0x4d
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"Miscellaneous": [
|
|
|
|
{
|
|
|
|
name: "UTF-8 text file",
|
|
|
|
extension: "txt",
|
|
|
|
mime: "text/plain",
|
|
|
|
description: "UTF-8 encoded Unicode byte order mark, commonly but not exclusively seen in text files.",
|
|
|
|
signature: {
|
|
|
|
0: 0xef,
|
|
|
|
1: 0xbb,
|
|
|
|
2: 0xbf
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{ // Place before UTF-16 LE file
|
|
|
|
name: "UTF-32 LE file",
|
|
|
|
extension: "utf32le",
|
|
|
|
mime: "charset/utf32le",
|
|
|
|
description: "Little-endian UTF-32 encoded Unicode byte order mark.",
|
|
|
|
signature: {
|
|
|
|
0: 0xff,
|
|
|
|
1: 0xfe,
|
|
|
|
2: 0x00,
|
|
|
|
3: 0x00
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "UTF-16 LE file",
|
|
|
|
extension: "utf16le",
|
|
|
|
mime: "charset/utf16le",
|
|
|
|
description: "Little-endian UTF-16 encoded Unicode byte order mark.",
|
|
|
|
signature: {
|
|
|
|
0: 0xff,
|
|
|
|
1: 0xfe
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Web Open Font Format",
|
|
|
|
extension: "woff",
|
|
|
|
mime: "application/font-woff",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x77,
|
|
|
|
1: 0x4f,
|
|
|
|
2: 0x46,
|
|
|
|
3: 0x46,
|
|
|
|
4: 0x0,
|
|
|
|
5: 0x1,
|
|
|
|
6: 0x0,
|
|
|
|
7: 0x0
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Web Open Font Format 2",
|
|
|
|
extension: "woff2",
|
|
|
|
mime: "application/font-woff",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x77,
|
|
|
|
1: 0x4f,
|
|
|
|
2: 0x46,
|
|
|
|
3: 0x32,
|
|
|
|
4: 0x0,
|
|
|
|
5: 0x1,
|
|
|
|
6: 0x0,
|
|
|
|
7: 0x0
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Embedded OpenType font",
|
|
|
|
extension: "eot",
|
|
|
|
mime: "application/octet-stream",
|
|
|
|
description: "",
|
|
|
|
signature: [
|
|
|
|
{
|
|
|
|
8: 0x2,
|
|
|
|
9: 0x0,
|
|
|
|
10: 0x1,
|
|
|
|
34: 0x4c,
|
|
|
|
35: 0x50
|
|
|
|
},
|
|
|
|
{
|
|
|
|
8: 0x1,
|
|
|
|
9: 0x0,
|
|
|
|
10: 0x0,
|
|
|
|
34: 0x4c,
|
|
|
|
35: 0x50
|
|
|
|
},
|
|
|
|
{
|
|
|
|
8: 0x2,
|
|
|
|
9: 0x0,
|
|
|
|
10: 0x2,
|
|
|
|
34: 0x4c,
|
|
|
|
35: 0x50
|
|
|
|
},
|
|
|
|
],
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "TrueType Font",
|
|
|
|
extension: "ttf",
|
|
|
|
mime: "application/font-sfnt",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x0,
|
|
|
|
1: 0x1,
|
|
|
|
2: 0x0,
|
|
|
|
3: 0x0,
|
|
|
|
4: 0x0
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "OpenType Font",
|
|
|
|
extension: "otf",
|
|
|
|
mime: "application/font-sfnt",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x4f,
|
|
|
|
1: 0x54,
|
|
|
|
2: 0x54,
|
|
|
|
3: 0x4f,
|
|
|
|
4: 0x0
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "SQLite",
|
|
|
|
extension: "sqlite",
|
|
|
|
mime: "application/x-sqlite3",
|
|
|
|
description: "",
|
|
|
|
signature: {
|
|
|
|
0: 0x53,
|
|
|
|
1: 0x51,
|
|
|
|
2: 0x4c,
|
|
|
|
3: 0x69
|
|
|
|
},
|
|
|
|
extractor: null
|
|
|
|
},
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* JPEG extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractJPEG(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
while (stream.hasMore()) {
|
|
|
|
const marker = stream.getBytes(2);
|
2019-03-02 16:12:21 +00:00
|
|
|
if (marker[0] !== 0xff) throw new Error(`Invalid marker while parsing JPEG at pos ${stream.position}: ${marker}`);
|
2018-12-26 23:19:46 +00:00
|
|
|
|
|
|
|
let segmentSize = 0;
|
|
|
|
switch (marker[1]) {
|
|
|
|
// No length
|
|
|
|
case 0xd8: // Start of Image
|
|
|
|
case 0x01: // For temporary use in arithmetic coding
|
|
|
|
break;
|
|
|
|
case 0xd9: // End found
|
|
|
|
return stream.carve();
|
|
|
|
|
|
|
|
// Variable size segment
|
|
|
|
case 0xc0: // Start of frame (Baseline DCT)
|
|
|
|
case 0xc1: // Start of frame (Extended sequential DCT)
|
|
|
|
case 0xc2: // Start of frame (Progressive DCT)
|
|
|
|
case 0xc3: // Start of frame (Lossless sequential)
|
|
|
|
case 0xc4: // Define Huffman Table
|
|
|
|
case 0xc5: // Start of frame (Differential sequential DCT)
|
|
|
|
case 0xc6: // Start of frame (Differential progressive DCT)
|
|
|
|
case 0xc7: // Start of frame (Differential lossless)
|
|
|
|
case 0xc8: // Reserved for JPEG extensions
|
|
|
|
case 0xc9: // Start of frame (Extended sequential DCT)
|
|
|
|
case 0xca: // Start of frame (Progressive DCT)
|
|
|
|
case 0xcb: // Start of frame (Lossless sequential)
|
|
|
|
case 0xcc: // Define arithmetic conditioning table
|
|
|
|
case 0xcd: // Start of frame (Differential sequential DCT)
|
|
|
|
case 0xce: // Start of frame (Differential progressive DCT)
|
|
|
|
case 0xcf: // Start of frame (Differential lossless)
|
|
|
|
case 0xdb: // Define Quantization Table
|
|
|
|
case 0xde: // Define hierarchical progression
|
|
|
|
case 0xe0: // Application-specific
|
|
|
|
case 0xe1: // Application-specific
|
|
|
|
case 0xe2: // Application-specific
|
|
|
|
case 0xe3: // Application-specific
|
|
|
|
case 0xe4: // Application-specific
|
|
|
|
case 0xe5: // Application-specific
|
|
|
|
case 0xe6: // Application-specific
|
|
|
|
case 0xe7: // Application-specific
|
|
|
|
case 0xe8: // Application-specific
|
|
|
|
case 0xe9: // Application-specific
|
|
|
|
case 0xea: // Application-specific
|
|
|
|
case 0xeb: // Application-specific
|
|
|
|
case 0xec: // Application-specific
|
|
|
|
case 0xed: // Application-specific
|
|
|
|
case 0xee: // Application-specific
|
|
|
|
case 0xef: // Application-specific
|
|
|
|
case 0xfe: // Comment
|
|
|
|
segmentSize = stream.readInt(2, "be");
|
|
|
|
stream.position += segmentSize - 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 1 byte
|
|
|
|
case 0xdf: // Expand reference image
|
|
|
|
stream.position++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 2 bytes
|
|
|
|
case 0xdc: // Define number of lines
|
|
|
|
case 0xdd: // Define restart interval
|
|
|
|
stream.position += 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Start scan
|
|
|
|
case 0xda: // Start of scan
|
|
|
|
segmentSize = stream.readInt(2, "be");
|
|
|
|
stream.position += segmentSize - 2;
|
|
|
|
stream.continueUntil(0xff);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Continue through encoded data
|
|
|
|
case 0x00: // Byte stuffing
|
|
|
|
case 0xd0: // Restart
|
|
|
|
case 0xd1: // Restart
|
|
|
|
case 0xd2: // Restart
|
|
|
|
case 0xd3: // Restart
|
|
|
|
case 0xd4: // Restart
|
|
|
|
case 0xd5: // Restart
|
|
|
|
case 0xd6: // Restart
|
|
|
|
case 0xd7: // Restart
|
|
|
|
stream.continueUntil(0xff);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
stream.continueUntil(0xff);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error("Unable to parse JPEG successfully");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Portable executable extractor.
|
|
|
|
* Assumes that the offset refers to an MZ header.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractMZPE(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Move to PE header pointer
|
|
|
|
stream.moveTo(0x3c);
|
|
|
|
const peAddress = stream.readInt(4, "le");
|
|
|
|
|
|
|
|
// Move to PE header
|
|
|
|
stream.moveTo(peAddress);
|
|
|
|
|
|
|
|
// Get number of sections
|
|
|
|
stream.moveForwardsBy(6);
|
|
|
|
const numSections = stream.readInt(2, "le");
|
|
|
|
|
|
|
|
// Get optional header size
|
|
|
|
stream.moveForwardsBy(12);
|
|
|
|
const optionalHeaderSize = stream.readInt(2, "le");
|
|
|
|
|
|
|
|
// Move past optional header to section header
|
|
|
|
stream.moveForwardsBy(2 + optionalHeaderSize);
|
|
|
|
|
|
|
|
// Move to final section header
|
|
|
|
stream.moveForwardsBy((numSections - 1) * 0x28);
|
|
|
|
|
|
|
|
// Get raw data info
|
|
|
|
stream.moveForwardsBy(16);
|
|
|
|
const rawDataSize = stream.readInt(4, "le");
|
|
|
|
const rawDataAddress = stream.readInt(4, "le");
|
|
|
|
|
|
|
|
// Move to end of final section
|
|
|
|
stream.moveTo(rawDataAddress + rawDataSize);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* PDF extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractPDF(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Find end-of-file marker (%%EOF)
|
|
|
|
stream.continueUntil([0x25, 0x25, 0x45, 0x4f, 0x46]);
|
|
|
|
stream.moveForwardsBy(5);
|
|
|
|
stream.consumeIf(0x0d);
|
|
|
|
stream.consumeIf(0x0a);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ZIP extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractZIP(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Find End of central directory record
|
|
|
|
stream.continueUntil([0x50, 0x4b, 0x05, 0x06]);
|
|
|
|
|
|
|
|
// Get comment length and consume
|
|
|
|
stream.moveForwardsBy(20);
|
|
|
|
const commentLength = stream.readInt(2, "le");
|
|
|
|
stream.moveForwardsBy(commentLength);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
2018-12-30 02:21:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* PNG extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractPNG(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Move past signature to first chunk
|
|
|
|
stream.moveForwardsBy(8);
|
|
|
|
|
|
|
|
let chunkSize = 0,
|
|
|
|
chunkType = "";
|
|
|
|
|
|
|
|
while (chunkType !== "IEND") {
|
|
|
|
chunkSize = stream.readInt(4, "be");
|
|
|
|
chunkType = stream.readString(4);
|
|
|
|
|
|
|
|
// Chunk data size + CRC checksum
|
|
|
|
stream.moveForwardsBy(chunkSize + 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* BMP extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractBMP(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Move past header
|
|
|
|
stream.moveForwardsBy(2);
|
|
|
|
|
|
|
|
// Read full file size
|
|
|
|
const bmpSize = stream.readInt(4, "le");
|
|
|
|
|
|
|
|
// Move to end of file (file size minus header and size field)
|
|
|
|
stream.moveForwardsBy(bmpSize - 6);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
2019-01-03 18:40:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* FLV extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractFLV(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Move past signature, version and flags
|
|
|
|
stream.moveForwardsBy(5);
|
|
|
|
|
|
|
|
// Read header size
|
|
|
|
const headerSize = stream.readInt(4, "be");
|
|
|
|
|
|
|
|
// Skip through the rest of the header
|
|
|
|
stream.moveForwardsBy(headerSize - 9);
|
|
|
|
|
|
|
|
let tagSize = -11; // Fake size of previous tag header
|
2019-01-04 14:57:31 +00:00
|
|
|
while (stream.hasMore()) {
|
2019-01-03 18:40:22 +00:00
|
|
|
const prevTagSize = stream.readInt(4, "be");
|
2019-01-04 18:12:49 +00:00
|
|
|
const tagType = stream.readInt(1);
|
2019-01-03 18:40:22 +00:00
|
|
|
|
|
|
|
if ([8, 9, 18].indexOf(tagType) < 0) {
|
|
|
|
// This tag is not valid
|
|
|
|
stream.moveBackwardsBy(1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-01-04 11:49:12 +00:00
|
|
|
if (prevTagSize !== (tagSize + 11)) {
|
|
|
|
// Previous tag was not valid, reverse back over this header
|
|
|
|
// and the previous tag body and header
|
|
|
|
stream.moveBackwardsBy(tagSize + 11 + 5);
|
2019-01-03 18:40:22 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tagSize = stream.readInt(3, "be");
|
|
|
|
|
|
|
|
// Move past the rest of the tag header and payload
|
|
|
|
stream.moveForwardsBy(7 + tagSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
2019-01-04 14:57:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* RTF extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractRTF(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
let openTags = 0;
|
|
|
|
|
2019-01-04 18:12:49 +00:00
|
|
|
if (stream.readInt(1) !== 0x7b) { // {
|
2019-01-04 14:57:31 +00:00
|
|
|
throw new Error("Not a valid RTF file");
|
|
|
|
} else {
|
|
|
|
openTags++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (openTags > 0 && stream.hasMore()) {
|
2019-01-04 18:12:49 +00:00
|
|
|
switch (stream.readInt(1)) {
|
2019-01-04 14:57:31 +00:00
|
|
|
case 0x7b: // {
|
|
|
|
openTags++;
|
|
|
|
break;
|
|
|
|
case 0x7d: // }
|
|
|
|
openTags--;
|
|
|
|
break;
|
|
|
|
case 0x5c: // \
|
|
|
|
// Consume any more escapes and then skip over the next character
|
|
|
|
stream.consumeIf(0x5c);
|
|
|
|
stream.position++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
2019-01-04 18:12:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GZIP extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractGZIP(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
|
2019-01-04 18:12:49 +00:00
|
|
|
/* HEADER */
|
|
|
|
|
|
|
|
// Skip over signature and compression method
|
|
|
|
stream.moveForwardsBy(3);
|
|
|
|
|
|
|
|
// Read flags
|
|
|
|
const flags = stream.readInt(1);
|
|
|
|
|
|
|
|
// Skip over last modification time
|
|
|
|
stream.moveForwardsBy(4);
|
|
|
|
|
|
|
|
// Read compression flags
|
2019-01-11 17:44:13 +00:00
|
|
|
stream.readInt(1);
|
2019-01-04 18:12:49 +00:00
|
|
|
|
|
|
|
// Skip over OS
|
|
|
|
stream.moveForwardsBy(1);
|
|
|
|
|
|
|
|
|
|
|
|
/* OPTIONAL HEADERS */
|
|
|
|
|
|
|
|
// Extra fields
|
|
|
|
if (flags & 0x4) {
|
|
|
|
const extraFieldsSize = stream.readInt(2, "le");
|
|
|
|
stream.moveForwardsby(extraFieldsSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Original filename
|
|
|
|
if (flags & 0x8) {
|
|
|
|
stream.continueUntil(0x00);
|
|
|
|
stream.moveForwardsBy(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Comment
|
|
|
|
if (flags & 0x10) {
|
|
|
|
stream.continueUntil(0x00);
|
|
|
|
stream.moveForwardsBy(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checksum
|
|
|
|
if (flags & 0x2) {
|
|
|
|
stream.moveForwardsBy(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* DEFLATE DATA */
|
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
parseDEFLATE(stream);
|
|
|
|
|
|
|
|
|
|
|
|
/* FOOTER */
|
|
|
|
|
|
|
|
// Skip over checksum and size of original uncompressed input
|
|
|
|
stream.moveForwardsBy(8);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-11 17:58:25 +00:00
|
|
|
/**
|
|
|
|
* Zlib extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractZlib(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Skip over CMF
|
|
|
|
stream.moveForwardsBy(1);
|
|
|
|
|
|
|
|
// Read flags
|
|
|
|
const flags = stream.readInt(1);
|
|
|
|
|
|
|
|
// Skip over preset dictionary checksum
|
|
|
|
if (flags & 0x20) {
|
|
|
|
stream.moveForwardsBy(4);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse DEFLATE stream
|
|
|
|
parseDEFLATE(stream);
|
|
|
|
|
|
|
|
// Skip over final checksum
|
|
|
|
stream.moveForwardsBy(4);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-14 18:55:10 +00:00
|
|
|
/**
|
|
|
|
* ELF extractor.
|
|
|
|
*
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {Uint8Array}
|
|
|
|
*/
|
|
|
|
export function extractELF(bytes, offset) {
|
|
|
|
const stream = new Stream(bytes.slice(offset));
|
|
|
|
|
|
|
|
// Skip over magic number
|
|
|
|
stream.moveForwardsBy(4);
|
|
|
|
|
|
|
|
// Read architecture (x86 == 1, x64 == 2)
|
|
|
|
const x86 = stream.readInt(1) === 1;
|
|
|
|
|
|
|
|
// Read endianness (1 == little, 2 == big)
|
|
|
|
const endian = stream.readInt(1) === 1 ? "le" : "be";
|
|
|
|
|
|
|
|
// Skip over header values
|
|
|
|
stream.moveForwardsBy(x86 ? 26 : 34);
|
|
|
|
|
|
|
|
// Read section header table offset
|
|
|
|
const shoff = x86 ? stream.readInt(4, endian) : stream.readInt(8, endian);
|
|
|
|
|
|
|
|
// Skip over flags, header size and program header size and entries
|
|
|
|
stream.moveForwardsBy(10);
|
|
|
|
|
|
|
|
// Read section header table entry size
|
|
|
|
const shentsize = stream.readInt(2, endian);
|
|
|
|
|
|
|
|
// Read number of entries in the section header table
|
|
|
|
const shnum = stream.readInt(2, endian);
|
|
|
|
|
|
|
|
// Jump to section header table
|
|
|
|
stream.moveTo(shoff);
|
|
|
|
|
|
|
|
// Move past each section header
|
|
|
|
stream.moveForwardsBy(shentsize * shnum);
|
|
|
|
|
|
|
|
return stream.carve();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-09 06:25:27 +00:00
|
|
|
// Construct required Huffman Tables
|
|
|
|
const fixedLiteralTableLengths = new Array(288);
|
|
|
|
for (let i = 0; i < fixedLiteralTableLengths.length; i++) {
|
|
|
|
fixedLiteralTableLengths[i] =
|
|
|
|
(i <= 143) ? 8 :
|
|
|
|
(i <= 255) ? 9 :
|
|
|
|
(i <= 279) ? 7 :
|
|
|
|
8;
|
|
|
|
}
|
|
|
|
const fixedLiteralTable = buildHuffmanTable(fixedLiteralTableLengths);
|
|
|
|
const fixedDistanceTableLengths = new Array(30).fill(5);
|
|
|
|
const fixedDistanceTable = buildHuffmanTable(fixedDistanceTableLengths);
|
|
|
|
const huffmanOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
/**
|
|
|
|
* Steps through a DEFLATE stream
|
|
|
|
*
|
|
|
|
* @param {Stream} stream
|
|
|
|
*/
|
|
|
|
function parseDEFLATE(stream) {
|
|
|
|
// Parse DEFLATE data
|
2019-01-04 18:12:49 +00:00
|
|
|
let finalBlock = 0;
|
|
|
|
|
|
|
|
while (!finalBlock) {
|
|
|
|
// Read header
|
2019-01-10 17:30:52 +00:00
|
|
|
finalBlock = stream.readBits(1);
|
|
|
|
const blockType = stream.readBits(2);
|
2019-01-04 18:12:49 +00:00
|
|
|
|
|
|
|
if (blockType === 0) {
|
2019-01-11 17:44:13 +00:00
|
|
|
/* No compression */
|
|
|
|
|
|
|
|
// Consume the rest of the current byte
|
2019-01-04 18:12:49 +00:00
|
|
|
stream.moveForwardsBy(1);
|
2019-01-11 17:44:13 +00:00
|
|
|
// Read the block length value
|
2019-01-04 18:12:49 +00:00
|
|
|
const blockLength = stream.readInt(2, "le");
|
2019-01-11 17:44:13 +00:00
|
|
|
// Move to the end of this block
|
2019-01-04 18:12:49 +00:00
|
|
|
stream.moveForwardsBy(2 + blockLength);
|
|
|
|
} else if (blockType === 1) {
|
2019-01-11 17:44:13 +00:00
|
|
|
/* Fixed Huffman */
|
2019-01-04 18:12:49 +00:00
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
parseHuffmanBlock(stream, fixedLiteralTable, fixedDistanceTable);
|
2019-01-04 18:12:49 +00:00
|
|
|
} else if (blockType === 2) {
|
2019-01-11 17:44:13 +00:00
|
|
|
/* Dynamic Huffman */
|
|
|
|
|
|
|
|
// Read the number of liternal and length codes
|
|
|
|
const hlit = stream.readBits(5) + 257;
|
|
|
|
// Read the number of distance codes
|
|
|
|
const hdist = stream.readBits(5) + 1;
|
|
|
|
// Read the number of code lengths
|
|
|
|
const hclen = stream.readBits(4) + 4;
|
|
|
|
|
|
|
|
// Parse code lengths
|
|
|
|
const codeLengths = new Uint8Array(huffmanOrder.length);
|
|
|
|
for (let i = 0; i < hclen; i++) {
|
|
|
|
codeLengths[huffmanOrder[i]] = stream.readBits(3);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse length table
|
|
|
|
const codeLengthsTable = buildHuffmanTable(codeLengths);
|
|
|
|
const lengthTable = new Uint8Array(hlit + hdist);
|
|
|
|
|
|
|
|
let code, repeat, prev;
|
|
|
|
for (let i = 0; i < hlit + hdist;) {
|
|
|
|
code = readHuffmanCode(stream, codeLengthsTable);
|
|
|
|
switch (code) {
|
|
|
|
case 16:
|
|
|
|
repeat = 3 + stream.readBits(2);
|
|
|
|
while (repeat--) lengthTable[i++] = prev;
|
|
|
|
break;
|
|
|
|
case 17:
|
|
|
|
repeat = 3 + stream.readBits(3);
|
|
|
|
while (repeat--) lengthTable[i++] = 0;
|
|
|
|
prev = 0;
|
|
|
|
break;
|
|
|
|
case 18:
|
|
|
|
repeat = 11 + stream.readBits(7);
|
|
|
|
while (repeat--) lengthTable[i++] = 0;
|
|
|
|
prev = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
lengthTable[i++] = code;
|
|
|
|
prev = code;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const dynamicLiteralTable = buildHuffmanTable(lengthTable.subarray(0, hlit));
|
|
|
|
const dynamicDistanceTable = buildHuffmanTable(lengthTable.subarray(hlit));
|
|
|
|
|
|
|
|
parseHuffmanBlock(stream, dynamicLiteralTable, dynamicDistanceTable);
|
2019-01-04 18:12:49 +00:00
|
|
|
} else {
|
2019-03-02 16:12:21 +00:00
|
|
|
throw new Error(`Invalid block type while parsing DEFLATE stream at pos ${stream.position}`);
|
2019-01-04 18:12:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
// Consume final byte if it has not been fully consumed yet
|
|
|
|
if (stream.bitPos > 0)
|
|
|
|
stream.moveForwardsBy(1);
|
|
|
|
}
|
2019-01-04 18:12:49 +00:00
|
|
|
|
|
|
|
|
2019-03-09 06:25:27 +00:00
|
|
|
// Static length tables
|
|
|
|
const lengthExtraTable = [
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0
|
|
|
|
];
|
|
|
|
const distanceExtraTable = [
|
|
|
|
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
|
|
|
|
];
|
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
/**
|
|
|
|
* Parses a Huffman Block given the literal and distance tables
|
|
|
|
*
|
|
|
|
* @param {Stream} stream
|
|
|
|
* @param {Uint32Array} litTab
|
|
|
|
* @param {Uint32Array} distTab
|
|
|
|
*/
|
|
|
|
function parseHuffmanBlock(stream, litTab, distTab) {
|
|
|
|
let code;
|
2019-03-09 06:25:27 +00:00
|
|
|
let loops = 0;
|
2019-01-11 17:44:13 +00:00
|
|
|
while ((code = readHuffmanCode(stream, litTab))) {
|
|
|
|
// console.log("Code: " + code + " (" + Utils.chr(code) + ") " + Utils.bin(code));
|
|
|
|
|
|
|
|
// End of block
|
|
|
|
if (code === 256) break;
|
|
|
|
|
2019-03-09 06:25:27 +00:00
|
|
|
// Detect probably infinite loops
|
|
|
|
if (++loops > 10000)
|
|
|
|
throw new Error("Caught in probable infinite loop while parsing Huffman Block");
|
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
// Literal
|
|
|
|
if (code < 256) continue;
|
|
|
|
|
|
|
|
// Length code
|
|
|
|
stream.readBits(lengthExtraTable[code - 257]);
|
|
|
|
|
|
|
|
// Dist code
|
|
|
|
code = readHuffmanCode(stream, distTab);
|
|
|
|
stream.readBits(distanceExtraTable[code]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 18:12:49 +00:00
|
|
|
|
2019-01-11 17:44:13 +00:00
|
|
|
/**
|
|
|
|
* Builds a Huffman table given the relevant code lengths
|
|
|
|
*
|
2019-03-09 06:25:27 +00:00
|
|
|
* @param {Array} lengths
|
2019-01-11 17:44:13 +00:00
|
|
|
* @returns {Array} result
|
|
|
|
* @returns {Uint32Array} result.table
|
|
|
|
* @returns {number} result.maxCodeLength
|
|
|
|
* @returns {number} result.minCodeLength
|
|
|
|
*/
|
|
|
|
function buildHuffmanTable(lengths) {
|
|
|
|
const maxCodeLength = Math.max.apply(Math, lengths);
|
|
|
|
const minCodeLength = Math.min.apply(Math, lengths);
|
|
|
|
const size = 1 << maxCodeLength;
|
|
|
|
const table = new Uint32Array(size);
|
|
|
|
|
|
|
|
for (let bitLength = 1, code = 0, skip = 2; bitLength <= maxCodeLength;) {
|
|
|
|
for (let i = 0; i < lengths.length; i++) {
|
|
|
|
if (lengths[i] === bitLength) {
|
|
|
|
let reversed, rtemp, j;
|
|
|
|
for (reversed = 0, rtemp = code, j = 0; j < bitLength; j++) {
|
|
|
|
reversed = (reversed << 1) | (rtemp & 1);
|
|
|
|
rtemp >>= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const value = (bitLength << 16) | i;
|
|
|
|
for (let j = reversed; j < size; j += skip) {
|
|
|
|
table[j] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
code++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bitLength++;
|
|
|
|
code <<= 1;
|
|
|
|
skip <<= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [table, maxCodeLength, minCodeLength];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the next Huffman code from the stream, given the relevant code table
|
|
|
|
*
|
|
|
|
* @param {Stream} stream
|
|
|
|
* @param {Uint32Array} table
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readHuffmanCode(stream, table) {
|
|
|
|
const [codeTable, maxCodeLength] = table;
|
|
|
|
|
|
|
|
// Read max length
|
|
|
|
const bitsBuf = stream.readBits(maxCodeLength);
|
|
|
|
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
|
|
|
|
const codeLength = codeWithLength >>> 16;
|
|
|
|
|
|
|
|
if (codeLength > maxCodeLength) {
|
2019-03-02 16:12:21 +00:00
|
|
|
throw new Error(`Invalid Huffman Code length while parsing DEFLATE block at pos ${stream.position}: ${codeLength}`);
|
2019-01-11 17:44:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stream.moveBackwardsByBits(maxCodeLength - codeLength);
|
|
|
|
|
|
|
|
return codeWithLength & 0xffff;
|
2019-01-04 18:12:49 +00:00
|
|
|
}
|