• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #388

14 Nov 2025 02:22PM UTC coverage: 80.685% (+0.05%) from 80.64%
#388

push

ebourg
More detailed error message when a corrupted certificate table is detected in PE files

5 of 5 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

4971 of 6161 relevant lines covered (80.68%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.7
/jsign-core/src/main/java/net/jsign/zip/CentralDirectoryFileHeader.java
1
/*
2
 * Copyright 2023 Emmanuel Bourg
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign.zip;
18

19
import java.io.IOException;
20
import java.nio.ByteBuffer;
21
import java.nio.channels.ReadableByteChannel;
22
import java.util.LinkedHashMap;
23
import java.util.Map;
24

25
import static java.nio.ByteOrder.*;
26

27
/**
28
 * Central Directory File Header:
29
 *
30
 * <pre>
31
 * central file header signature   4 bytes  (0x02014b50)
32
 * version made by                 2 bytes
33
 * version needed to extract       2 bytes
34
 * general purpose bit flag        2 bytes
35
 * compression method              2 bytes
36
 * last mod file time              2 bytes
37
 * last mod file date              2 bytes
38
 * crc-32                          4 bytes
39
 * compressed size                 4 bytes
40
 * uncompressed size               4 bytes
41
 * file name length                2 bytes
42
 * extra field length              2 bytes
43
 * file comment length             2 bytes
44
 * disk number start               2 bytes
45
 * internal file attributes        2 bytes
46
 * external file attributes        4 bytes
47
 * relative offset of local header 4 bytes
48
 *
49
 * file name (variable size)
50
 * extra field (variable size)
51
 * file comment (variable size)
52
 * </pre>
53
 *
54
 * @since 6.0
55
 */
56
public class CentralDirectoryFileHeader extends ZipRecord {
1✔
57

58
    public static final int SIGNATURE = 0x02014b50;
59
    private static final int MIN_SIZE = 46;
60

61
    public int versionMadeBy;
62
    public int versionNeededToExtract;
63
    public int generalPurposeBitFlag;
64
    public int compressionMethod;
65
    public int lastModFileTime;
66
    public int lastModFileDate;
67
    public int crc32;
68
    public long compressedSize;
69
    public long uncompressedSize;
70
    public int diskNumberStart;
71
    public int internalFileAttributes;
72
    public int externalFileAttributes;
73
    public long localHeaderOffset;
74
    public byte[] fileName = new byte[0];
1✔
75
    public byte[] fileComment = new byte[0];
1✔
76

77
    public Map<Integer, ExtraField> extraFields = new LinkedHashMap<>();
1✔
78

79
    @Override
80
    public void read(ReadableByteChannel channel) throws IOException {
81
        ByteBuffer buffer = ByteBuffer.allocate(MIN_SIZE).order(LITTLE_ENDIAN);
1✔
82
        channel.read(buffer);
1✔
83
        buffer.flip();
1✔
84
        if (buffer.remaining() < MIN_SIZE) {
1✔
85
            throw new IOException("Invalid Central Directory File Header");
1✔
86
        }
87

88
        int signature = buffer.getInt();
1✔
89
        if (signature != SIGNATURE) {
1✔
90
            throw new IOException("Invalid Central Directory File Header signature " + String.format("0x%04x", signature & 0xFFFFFFFFL));
1✔
91
        }
92
        versionMadeBy = buffer.getShort();
1✔
93
        versionNeededToExtract = buffer.getShort();
1✔
94
        generalPurposeBitFlag = buffer.getShort();
1✔
95
        compressionMethod = buffer.getShort();
1✔
96
        lastModFileTime = buffer.getShort();
1✔
97
        lastModFileDate = buffer.getShort();
1✔
98
        crc32 = buffer.getInt();
1✔
99
        compressedSize = buffer.getInt() & 0xFFFFFFFFL;
1✔
100
        uncompressedSize = buffer.getInt() & 0xFFFFFFFFL;
1✔
101
        int fileNameLength = buffer.getShort() & 0xFFFF;
1✔
102
        int extraFieldsLength = buffer.getShort() & 0xFFFF;
1✔
103
        int fileCommentLength = buffer.getShort() & 0xFFFF;
1✔
104
        diskNumberStart = buffer.getShort();
1✔
105
        internalFileAttributes = buffer.getShort();
1✔
106
        externalFileAttributes = buffer.getInt();
1✔
107
        localHeaderOffset = buffer.getInt() & 0xFFFFFFFFL;
1✔
108
        if (fileNameLength > 0) {
1✔
109
            fileName = new byte[fileNameLength];
1✔
110
            channel.read(ByteBuffer.wrap(fileName));
1✔
111
        }
112
        if (extraFieldsLength > 0) {
1✔
113
            byte[] extraFields = new byte[extraFieldsLength];
1✔
114
            channel.read(ByteBuffer.wrap(extraFields));
1✔
115

116
            this.extraFields = ExtraField.parseAll(ByteBuffer.wrap(extraFields).order(LITTLE_ENDIAN),
1✔
117
                    uncompressedSize == 0xFFFFFFFFL,
118
                    compressedSize == 0xFFFFFFFFL,
119
                    localHeaderOffset == 0xFFFFFFFFL,
120
                    diskNumberStart == 0xFFFF);
121
        }
122
        if (fileCommentLength > 0) {
1✔
123
            fileComment = new byte[fileCommentLength];
×
124
            channel.read(ByteBuffer.wrap(fileComment));
×
125
        }
126

127
        // validate the offset and sizes
128
        if (!extraFields.containsKey(1) && (localHeaderOffset == 0xFFFFFFFFL || compressedSize == 0xFFFFFFFFL || uncompressedSize == 0xFFFFFFFFL)) {
1✔
129
            throw new IOException("Missing ZIP64 extra field in the Central Directory File Header");
1✔
130
        }
131
    }
1✔
132

133
    private int getExtraFieldsLength() {
134
        int length = 0;
1✔
135
        for (ExtraField field : extraFields.values()) {
1✔
136
            length += field.size();
1✔
137
        }
1✔
138
        return length;
1✔
139
    }
140

141
    @Override
142
    public ByteBuffer toBuffer() {
143
        ByteBuffer buffer = ByteBuffer.allocate(MIN_SIZE + fileName.length + getExtraFieldsLength() + fileComment.length).order(LITTLE_ENDIAN);
1✔
144
        buffer.putInt(SIGNATURE);
1✔
145
        buffer.putShort((short) versionMadeBy);
1✔
146
        buffer.putShort((short) versionNeededToExtract);
1✔
147
        buffer.putShort((short) generalPurposeBitFlag);
1✔
148
        buffer.putShort((short) compressionMethod);
1✔
149
        buffer.putShort((short) lastModFileTime);
1✔
150
        buffer.putShort((short) lastModFileDate);
1✔
151
        buffer.putInt(crc32);
1✔
152
        buffer.putInt((int) compressedSize);
1✔
153
        buffer.putInt((int) uncompressedSize);
1✔
154
        buffer.putShort((short) fileName.length);
1✔
155
        buffer.putShort((short) getExtraFieldsLength());
1✔
156
        buffer.putShort((short) fileComment.length);
1✔
157
        buffer.putShort((short) diskNumberStart);
1✔
158
        buffer.putShort((short) internalFileAttributes);
1✔
159
        buffer.putInt(externalFileAttributes);
1✔
160
        buffer.putInt((int) localHeaderOffset);
1✔
161
        buffer.put(fileName);
1✔
162
        if (!extraFields.isEmpty()) {
1✔
163
            for (ExtraField field : extraFields.values()) {
1✔
164
                field.write(buffer);
1✔
165
            }
1✔
166
        }
167
        buffer.put(fileComment);
1✔
168
        buffer.flip();
1✔
169

170
        return buffer;
1✔
171
    }
172

173
    public long getCompressedSize() {
174
        if (compressedSize == 0xFFFFFFFFL) {
1✔
175
            Zip64ExtendedInfoExtraField zip64ExtraField = (Zip64ExtendedInfoExtraField) extraFields.get(1);
1✔
176
            return zip64ExtraField.compressedSize;
1✔
177
        } else {
178
            return compressedSize;
1✔
179
        }
180
    }
181

182
    public long getUncompressedSize() {
183
        if (uncompressedSize == 0xFFFFFFFFL) {
1✔
184
            Zip64ExtendedInfoExtraField zip64ExtraField = (Zip64ExtendedInfoExtraField) extraFields.get(1);
1✔
185
            return zip64ExtraField.uncompressedSize;
1✔
186
        } else {
187
            return uncompressedSize;
1✔
188
        }
189
    }
190

191
    public long getLocalHeaderOffset() {
192
        if (localHeaderOffset == 0xFFFFFFFFL) {
1✔
193
            Zip64ExtendedInfoExtraField zip64ExtraField = (Zip64ExtendedInfoExtraField) extraFields.get(1);
1✔
194
            return zip64ExtraField.localHeaderOffset;
1✔
195
        } else {
196
            return localHeaderOffset;
1✔
197
        }
198
    }
199

200
    public void setLocalHeaderOffset(long offset) {
201
        if (offset > 0xFFFFFFFFL || localHeaderOffset == 0xFFFFFFFFL) {
1✔
202
            Zip64ExtendedInfoExtraField zip64ExtraField = (Zip64ExtendedInfoExtraField) extraFields.get(1);
1✔
203
            zip64ExtraField.localHeaderOffset = offset;
1✔
204
            localHeaderOffset = 0xFFFFFFFFL;
1✔
205
        } else {
1✔
UNCOV
206
            localHeaderOffset = offset;
×
207
        }
208
    }
1✔
209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc