Completed
Push — master ( 00d23f...d25395 )
by Jan
15s queued 12s
created

ID3Frame.js ➔ decompressBodyBuffer   B

Complexity

Conditions 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 31
rs 8.6166
c 0
b 0
f 0
cc 6
1
const zlib = require('zlib')
2
const ID3FrameHeader = require('./ID3FrameHeader')
3
const ID3Frames = require('./ID3Frames')
4
const ID3Util = require('./ID3Util')
5
6
class ID3Frame {
7
    constructor(identifier, value, flags = {}) {
8
        this.identifier = identifier
9
        this.value = value
10
        this.dataLengthIndicator = 0
11
        this.flags = flags
12
    }
13
14
    static createFromBuffer(frameBuffer, version) {
15
        const frameHeaderSize = ID3FrameHeader.getHeaderSize(version)
16
        // Specification requirement
17
        if(frameBuffer < frameHeaderSize + 1) {
18
            return null
19
        }
20
        const frameHeaderBuffer = frameBuffer.subarray(0, frameHeaderSize)
21
        const frameHeader = ID3FrameHeader.createFromBuffer(frameHeaderBuffer, version)
22
        if(frameHeader.flags.encryption) {
23
            return null
24
        }
25
26
        const frameBodyOffset = frameHeader.flags.dataLengthIndicator ? 4 : 0
27
        const frameBodyStart = frameHeaderSize + frameBodyOffset
28
        let frameBody = frameBuffer.subarray(frameBodyStart, frameBodyStart + frameHeader.bodySize - frameBodyOffset)
29
        if(frameHeader.flags.unsynchronisation) {
30
            // This method should stay in ID3Util for now because it's also used in the Tag's header which we don't have a class for.
31
            frameBody = ID3Util.processUnsynchronisedBuffer(frameBody)
32
        }
33
        if(frameHeader.flags.dataLengthIndicator) {
34
            this.dataLengthIndicator = frameBuffer.readInt32BE(frameHeaderSize)
35
        }
36
        if(frameHeader.flags.compression) {
37
            const uncompressedFrameBody = decompressBodyBuffer(frameBody, this.dataLengthIndicator)
38
            if(!uncompressedFrameBody) {
39
                return null
40
            }
41
            frameBody = uncompressedFrameBody
42
        }
43
44
        let value = null
0 ignored issues
show
Unused Code introduced by
The assignment to value seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
45
        if(ID3Frames[frameHeader.identifier]) {
46
            value = ID3Frames[frameHeader.identifier].read(frameBody, version)
47
        } else if(frameHeader.identifier.startsWith('T')) {
48
            value = ID3Frames.GENERIC_TEXT.read(frameBody, version)
49
        } else if(frameHeader.identifier.startsWith('W')) {
50
            value = ID3Frames.GENERIC_URL.read(frameBody, version)
51
        } else {
52
            // Unknown frames are not supported currently.
53
            return null
54
        }
55
56
        return new ID3Frame(frameHeader.identifier, value, frameHeader.flags)
57
    }
58
59
    getBuffer() {
60
        if(ID3Frames[this.identifier]) {
61
            return ID3Frames[this.identifier].create(this.value)
62
        }
63
        if(this.identifier.startsWith('T')) {
64
            return ID3Frames.GENERIC_TEXT.create(this.value)
65
        }
66
        if(this.identifier.startsWith('W')) {
67
            return ID3Frames.GENERIC_URL.create(this.value)
68
        }
69
70
        return null
71
    }
72
73
    getValue() {
74
        return this.value
75
    }
76
}
77
78
function decompressBodyBuffer(bodyBuffer, dataLengthIndicator) {
79
    if(bodyBuffer.length < 5 || dataLengthIndicator === undefined) {
80
        return null
81
    }
82
83
    /*
84
    * ID3 spec defines that compression is stored in ZLIB format, but doesn't specify if header is present or not.
85
    * ZLIB has a 2-byte header.
86
    * 1. try if header + body decompression
87
    * 2. else try if header is not stored (assume that all content is deflated "body")
88
    * 3. else try if inflation works if the header is omitted (implementation dependent)
89
    * */
90
    let decompressedBody
91
    try {
92
        decompressedBody = zlib.inflateSync(bodyBuffer)
93
    } catch (e) {
94
        try {
95
            decompressedBody = zlib.inflateRawSync(bodyBuffer)
96
        } catch (e) {
97
            try {
98
                decompressedBody = zlib.inflateRawSync(bodyBuffer.subarray(2))
99
            } catch (e) {
100
                return null
101
            }
102
        }
103
    }
104
    if(decompressedBody.length !== dataLengthIndicator) {
105
        return null
106
    }
107
    return decompressedBody
108
}
109
110
module.exports = ID3Frame
111