Skip to content

Commit bea59e4

Browse files
committed
[COMPRESS-706] Add support for reading LHA archive format
1 parent 86c196e commit bea59e4

36 files changed

+3883
-12
lines changed

src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream;
3939
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
4040
import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
41+
import org.apache.commons.compress.archivers.lha.LhaArchiveInputStream;
4142
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
4243
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
4344
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@@ -91,6 +92,8 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
9192

9293
private static final int DUMP_SIGNATURE_SIZE = 32;
9394

95+
private static final int LHA_SIGNATURE_SIZE = 22;
96+
9497
private static final int SIGNATURE_SIZE = 12;
9598

9699
/**
@@ -175,6 +178,13 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
175178
*/
176179
public static final String JAR = "jar";
177180

181+
/**
182+
* Constant (value {@value}) used to identify the LHA archive format.
183+
* Not supported as an output stream type.
184+
* @since 1.29
185+
*/
186+
public static final String LHA = "lha";
187+
178188
/**
179189
* Constant used to identify the TAR archive format.
180190
*
@@ -244,6 +254,7 @@ public static String detect(final InputStream in) throws ArchiveException {
244254
if (SevenZFile.matches(signature, signatureLength)) {
245255
return SEVEN_Z;
246256
}
257+
247258
// Dump needs a bigger buffer to check the signature;
248259
final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE];
249260
in.mark(dumpsig.length);
@@ -256,6 +267,20 @@ public static String detect(final InputStream in) throws ArchiveException {
256267
if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) {
257268
return DUMP;
258269
}
270+
271+
// LHA needs a bigger buffer to check the signature
272+
final byte[] lhasig = new byte[LHA_SIGNATURE_SIZE];
273+
in.mark(lhasig.length);
274+
try {
275+
signatureLength = IOUtils.readFully(in, lhasig);
276+
in.reset();
277+
} catch (final IOException e) {
278+
throw new ArchiveException("IOException while reading LHA signature", (Throwable) e);
279+
}
280+
if (LhaArchiveInputStream.matches(lhasig, signatureLength)) {
281+
return LHA;
282+
}
283+
259284
// Tar needs an even bigger buffer to check the signature; read the first block
260285
final byte[] tarHeader = new byte[TAR_HEADER_SIZE];
261286
in.mark(tarHeader.length);
@@ -437,6 +462,12 @@ public <I extends ArchiveInputStream<? extends ArchiveEntry>> I createArchiveInp
437462
}
438463
return (I) new ArjArchiveInputStream(in);
439464
}
465+
if (LHA.equalsIgnoreCase(archiverName)) {
466+
if (actualEncoding != null) {
467+
return (I) new LhaArchiveInputStream(in, actualEncoding);
468+
}
469+
return (I) new LhaArchiveInputStream(in);
470+
}
440471
if (ZIP.equalsIgnoreCase(archiverName)) {
441472
if (actualEncoding != null) {
442473
return (I) new ZipArchiveInputStream(in, actualEncoding);
@@ -579,7 +610,7 @@ public String getEntryEncoding() {
579610

580611
@Override
581612
public Set<String> getInputStreamArchiveNames() {
582-
return Sets.newHashSet(AR, ARJ, ZIP, TAR, JAR, CPIO, DUMP, SEVEN_Z);
613+
return Sets.newHashSet(AR, ARJ, LHA, ZIP, TAR, JAR, CPIO, DUMP, SEVEN_Z);
583614
}
584615

585616
@Override

src/main/java/org/apache/commons/compress/archivers/ArchiveStreamProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public interface ArchiveStreamProvider {
3636
* @param <I> The {@link ArchiveInputStream} type.
3737
* @param archiverName the archiver name, i.e. {@value org.apache.commons.compress.archivers.ArchiveStreamFactory#AR},
3838
* {@value org.apache.commons.compress.archivers.ArchiveStreamFactory#ARJ},
39+
* {@value org.apache.commons.compress.archivers.ArchiveStreamFactory#LHA},
3940
* {@value org.apache.commons.compress.archivers.ArchiveStreamFactory#ZIP},
4041
* {@value org.apache.commons.compress.archivers.ArchiveStreamFactory#TAR},
4142
* {@value org.apache.commons.compress.archivers.ArchiveStreamFactory#JAR},
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.commons.compress.archivers.lha;
21+
22+
import java.time.ZoneOffset;
23+
import java.util.Date;
24+
import java.util.Optional;
25+
26+
import org.apache.commons.compress.archivers.ArchiveEntry;
27+
28+
/**
29+
* Represents an entry in a LHA archive.
30+
*
31+
* @since 1.29
32+
*/
33+
public class LhaArchiveEntry implements ArchiveEntry {
34+
private String name;
35+
private boolean directory;
36+
private long size;
37+
private Date lastModifiedDate;
38+
private long compressedSize;
39+
private String compressionMethod;
40+
private int crcValue;
41+
private Optional<Integer> osId = Optional.empty();
42+
private Optional<Integer> unixPermissionMode = Optional.empty();
43+
private Optional<Integer> unixUserId = Optional.empty();
44+
private Optional<Integer> unixGroupId = Optional.empty();
45+
private Optional<Integer> msdosFileAttributes = Optional.empty();
46+
private Optional<Integer> headerCrc = Optional.empty();
47+
48+
public LhaArchiveEntry() {
49+
}
50+
51+
@Override
52+
public String toString() {
53+
StringBuffer sb = new StringBuffer().append("LhaArchiveEntry[")
54+
.append("name=").append(name)
55+
.append(",directory=").append(directory)
56+
.append(",size=").append(size)
57+
.append(",lastModifiedDate=").append(lastModifiedDate == null ? "" : lastModifiedDate.toInstant().atZone(ZoneOffset.UTC).toString())
58+
.append(",compressedSize=").append(compressedSize)
59+
.append(",compressionMethod=").append(compressionMethod)
60+
.append(",crcValue=").append(String.format("0x%04x", crcValue));
61+
62+
if (osId.isPresent()) {
63+
sb.append(",osId=").append(osId.get());
64+
}
65+
66+
if (unixPermissionMode.isPresent()) {
67+
sb.append(",unixPermissionMode=").append(String.format("%03o", unixPermissionMode.get()));
68+
}
69+
70+
if (msdosFileAttributes.isPresent()) {
71+
sb.append(",msdosFileAttributes=").append(String.format("%04x", msdosFileAttributes.get()));
72+
}
73+
74+
if (headerCrc.isPresent()) {
75+
sb.append(",headerCrc=").append(String.format("0x%04x", headerCrc.get()));
76+
}
77+
78+
return sb.append("]").toString();
79+
}
80+
81+
@Override
82+
public String getName() {
83+
return name;
84+
}
85+
86+
public void setName(String name) {
87+
this.name = name;
88+
}
89+
90+
@Override
91+
public long getSize() {
92+
return size;
93+
}
94+
95+
public void setSize(long size) {
96+
this.size = size;
97+
}
98+
99+
@Override
100+
public Date getLastModifiedDate() {
101+
return lastModifiedDate;
102+
}
103+
104+
public void setLastModifiedDate(Date lastModifiedDate) {
105+
this.lastModifiedDate = lastModifiedDate;
106+
}
107+
108+
/**
109+
* Returns the compressed size of this entry.
110+
*
111+
* @return the compressed size
112+
*/
113+
public long getCompressedSize() {
114+
return compressedSize;
115+
}
116+
117+
public void setCompressedSize(long compressedSize) {
118+
this.compressedSize = compressedSize;
119+
}
120+
121+
public void setDirectory(boolean directory) {
122+
this.directory = directory;
123+
}
124+
125+
@Override
126+
public boolean isDirectory() {
127+
return directory;
128+
}
129+
130+
/**
131+
* Returns the compression method of this entry.
132+
*
133+
* @return the compression method
134+
*/
135+
public String getCompressionMethod() {
136+
return compressionMethod;
137+
}
138+
139+
public void setCompressionMethod(String compressionMethod) {
140+
this.compressionMethod = compressionMethod;
141+
}
142+
143+
/**
144+
* Returns the CRC-16 checksum of the uncompressed data of this entry.
145+
*
146+
* @return CRC-16 checksum of the uncompressed data
147+
*/
148+
public int getCrcValue() {
149+
return crcValue;
150+
}
151+
152+
public void setCrcValue(int crc) {
153+
this.crcValue = crc;
154+
}
155+
156+
/**
157+
* Returns the operating system id if available for this entry.
158+
*
159+
* @return operating system id if available
160+
*/
161+
public Optional<Integer> getOsId() {
162+
return osId;
163+
}
164+
165+
public void setOsId(Optional<Integer> osId) {
166+
this.osId = osId;
167+
}
168+
169+
public Optional<Integer> getUnixPermissionMode() {
170+
return unixPermissionMode;
171+
}
172+
173+
public void setUnixPermissionMode(Optional<Integer> unixPermissionMode) {
174+
this.unixPermissionMode = unixPermissionMode;
175+
}
176+
177+
public Optional<Integer> getUnixUserId() {
178+
return unixUserId;
179+
}
180+
181+
public void setUnixUserId(Optional<Integer> unixUserId) {
182+
this.unixUserId = unixUserId;
183+
}
184+
185+
public Optional<Integer> getUnixGroupId() {
186+
return unixGroupId;
187+
}
188+
189+
public void setUnixGroupId(Optional<Integer> unixGroupId) {
190+
this.unixGroupId = unixGroupId;
191+
}
192+
193+
/**
194+
* Returns the MS-DOS file attributes if available for this entry.
195+
*
196+
* @return MS-DOS file attributes if available
197+
*/
198+
public Optional<Integer> getMsdosFileAttributes() {
199+
return msdosFileAttributes;
200+
}
201+
202+
public void setMsdosFileAttributes(Optional<Integer> msdosFileAttributes) {
203+
this.msdosFileAttributes = msdosFileAttributes;
204+
}
205+
206+
/**
207+
* Don't expose the header CRC publicly, as it is of no interest to most users.
208+
*/
209+
Optional<Integer> getHeaderCrc() {
210+
return headerCrc;
211+
}
212+
213+
void setHeaderCrc(Optional<Integer> headerCrc) {
214+
this.headerCrc = headerCrc;
215+
}
216+
}

0 commit comments

Comments
 (0)