Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true

[*.java]
indent_size = 4
indent_style = tab
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ hs_err_pid*
*.log
*.ctxt
temp/
*.class

# IntelliJ
lib/
lib/
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package software.coley.recaf.info;

import java.lang.foreign.ValueLayout;

import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
import jakarta.annotation.Nonnull;
import software.coley.recaf.info.builder.ChunkFileInfoBuilder;
Expand All @@ -24,7 +26,7 @@ public BasicAndroidChunkFileInfo(ChunkFileInfoBuilder<?> builder) {
@Override
public BinaryResourceFile getChunkModel() {
if (resourceFile == null)
resourceFile = new BinaryResourceFile(getRawContent());
resourceFile = new BinaryResourceFile(getRawContent().toArray(ValueLayout.JAVA_BYTE));
return resourceFile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import software.coley.recaf.info.builder.FileInfoBuilder;
import software.coley.recaf.info.properties.Property;
import software.coley.recaf.info.properties.PropertyContainer;
import software.coley.recaf.util.MemorySegmentUtil;

import java.util.Arrays;
import java.lang.foreign.MemorySegment;
import java.util.Map;

/**
Expand All @@ -16,7 +17,7 @@
public class BasicFileInfo implements FileInfo {
private final PropertyContainer properties;
private final String name;
private final byte[] rawContent;
private final MemorySegment rawContent;

public BasicFileInfo(@Nonnull FileInfoBuilder<?> builder) {
this(builder.getName(),
Expand All @@ -32,15 +33,15 @@ public BasicFileInfo(@Nonnull FileInfoBuilder<?> builder) {
* @param properties
* Assorted properties.
*/
public BasicFileInfo(@Nonnull String name, @Nonnull byte[] rawContent, @Nonnull PropertyContainer properties) {
public BasicFileInfo(@Nonnull String name, @Nonnull MemorySegment rawContent, @Nonnull PropertyContainer properties) {
this.name = name;
this.rawContent = rawContent;
this.properties = properties;
}

@Nonnull
@Override
public byte[] getRawContent() {
public MemorySegment getRawContent() {
return rawContent;
}

Expand All @@ -56,15 +57,15 @@ public boolean equals(Object o) {
if (o == null) return false;
if (o instanceof FileInfo other) {
if (!name.equals(other.getName())) return false;
return Arrays.equals(rawContent, other.getRawContent());
return MemorySegmentUtil.equals(rawContent, other.getRawContent());
}
return false;
}

@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + Arrays.hashCode(rawContent);
result = 31 * result + rawContent.hashCode();
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package software.coley.recaf.info;

import java.lang.foreign.MemorySegment;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import software.coley.recaf.info.builder.FileInfoBuilder;
Expand All @@ -22,7 +24,7 @@ default FileInfoBuilder<?> toFileBuilder() {
* @return Raw bytes of file content.
*/
@Nonnull
byte[] getRawContent();
MemorySegment getRawContent();

/**
* @return File extension of {@link #getName() the file name}, if any exists.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;
import software.coley.recaf.util.StringUtil;

import java.lang.foreign.MemorySegment;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
Expand Down Expand Up @@ -53,8 +54,8 @@ public TextFileInfo withText(@Nonnull Charset charset, @Nonnull String text) {

@Nonnull
@Override
public byte[] getRawContent() {
return new byte[0];
public MemorySegment getRawContent() {
return MemorySegment.ofArray(new byte[0]);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package software.coley.recaf.info.builder;

import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;

import jakarta.annotation.Nonnull;
import software.coley.recaf.info.*;
import software.coley.recaf.info.properties.BasicPropertyContainer;
Expand All @@ -19,7 +22,7 @@
public class FileInfoBuilder<B extends FileInfoBuilder<?>> {
private PropertyContainer properties = new BasicPropertyContainer();
private String name;
private byte[] rawContent;
private MemorySegment rawContent;
protected StringDecodingResult decodingResult;

public FileInfoBuilder() {
Expand Down Expand Up @@ -89,12 +92,16 @@ public B withName(@Nonnull String name) {
}

@SuppressWarnings("unchecked")
public B withRawContent(@Nonnull byte[] rawContent) {
public B withRawContent(MemorySegment rawContent) {
this.rawContent = rawContent;
decodingResult = null; // Clear decoding when content changes
return (B) this;
}

public B withRawContent(@Nonnull byte[] rawContent) {
return withRawContent(MemorySegment.ofArray(rawContent));
}

public PropertyContainer getProperties() {
return properties;
}
Expand All @@ -103,7 +110,7 @@ public String getName() {
return name;
}

public byte[] getRawContent() {
public MemorySegment getRawContent() {
return rawContent;
}

Expand All @@ -113,7 +120,7 @@ public byte[] getRawContent() {
@Nonnull
protected StringDecodingResult getDecodingResult() {
if (decodingResult == null)
decodingResult = StringUtil.decodeString(rawContent);
decodingResult = StringUtil.decodeString(rawContent.toArray(ValueLayout.JAVA_BYTE));
return decodingResult;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import software.coley.recaf.info.TextFileInfo;
import software.coley.recaf.util.StringDecodingResult;

import java.lang.foreign.ValueLayout;
import java.nio.charset.Charset;
import java.util.Objects;

Expand All @@ -20,7 +21,7 @@ public TextFileInfoBuilder() {

public TextFileInfoBuilder(@Nonnull TextFileInfo textInfo) {
super(textInfo);
this.decodingResult = new StringDecodingResult(textInfo.getRawContent(), textInfo.getCharset(), textInfo.getText());
this.decodingResult = new StringDecodingResult(textInfo.getRawContent().toArray(ValueLayout.JAVA_BYTE), textInfo.getCharset(), textInfo.getText());
}

public TextFileInfoBuilder(@Nonnull FileInfoBuilder<?> other, @Nonnull StringDecodingResult decodingResult) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import software.coley.recaf.path.FilePathNode;
import software.coley.recaf.path.PathNodes;
import software.coley.recaf.services.Service;
import software.coley.recaf.util.MemorySegmentUtil;
import software.coley.recaf.util.io.LargeInputStream;
import software.coley.recaf.workspace.model.Workspace;
import software.coley.recaf.workspace.model.bundle.FileBundle;
import software.coley.recaf.workspace.model.resource.WorkspaceFileResource;
import software.coley.recaf.workspace.model.resource.WorkspaceResource;

import java.io.ByteArrayInputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
Expand Down Expand Up @@ -116,7 +117,7 @@ public JarSigningReport analyzeJarSigning(@Nonnull Workspace workspace,
parseError = "Certificate factory unavailable";
} else {
try {
certificates = List.copyOf(CERTIFICATE_FACTORY.generateCertificates(new ByteArrayInputStream(file.getRawContent())));
certificates = List.copyOf(CERTIFICATE_FACTORY.generateCertificates(new LargeInputStream(file.getRawContent())));
} catch (CertificateException ex) {
parseError = "Error parsing certificate: " + file.getName();
}
Expand All @@ -143,12 +144,12 @@ public FileMetadataAnalysisConfig getServiceConfig() {
@SuppressWarnings("deprecation") // Don't care that MD5/SHA1 are deprecated, people still use them frequently.
private static FileHashResult hash(@Nonnull Workspace workspace, @Nonnull WorkspaceFileResource fileResource) {
FileInfo fileInfo = fileResource.getFileInfo();
byte[] content = fileInfo.getRawContent();
var content = fileInfo.getRawContent();
EnumMap<HashAlgorithm, String> hashes = new EnumMap<>(HashAlgorithm.class);
hashes.put(HashAlgorithm.MD5, Hashing.md5().hashBytes(content).toString());
hashes.put(HashAlgorithm.SHA1, Hashing.sha1().hashBytes(content).toString());
hashes.put(HashAlgorithm.SHA256, Hashing.sha256().hashBytes(content).toString());
hashes.put(HashAlgorithm.SHA512, Hashing.sha512().hashBytes(content).toString());
hashes.put(HashAlgorithm.MD5, MemorySegmentUtil.hash(content, Hashing.md5()).toString());
hashes.put(HashAlgorithm.SHA1, MemorySegmentUtil.hash(content, Hashing.sha1()).toString());
hashes.put(HashAlgorithm.SHA256, MemorySegmentUtil.hash(content, Hashing.sha256()).toString());
hashes.put(HashAlgorithm.SHA512, MemorySegmentUtil.hash(content, Hashing.sha512()).toString());
FilePathNode path = PathNodes.filePath(workspace, fileResource, fileResource.getFileBundle(), fileInfo);
return new FileHashResult(path, new EnumMap<>(hashes));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.lang.foreign.ValueLayout;
import java.net.*;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -120,7 +121,7 @@ Class<?> lookupClassImpl(@Nonnull String name) throws ClassNotFoundException {
if (classBytes != null) {
byte[] bytes;
try {
bytes = classBytes.readAll();
bytes = classBytes.readAll().toArray(ValueLayout.JAVA_BYTE);
} catch (IOException ex) {
throw new ClassNotFoundException(name, ex);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package software.coley.recaf.services.workspace.io;

import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;

import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import me.darknet.dex.tree.codec.definition.CodeCodec;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import software.coley.cafedude.classfile.VersionConstants;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.info.FileInfo;
Expand All @@ -26,12 +31,11 @@
import software.coley.recaf.info.properties.builtin.IllegalClassSuspectProperty;
import software.coley.recaf.info.properties.builtin.ZipMarkerProperty;
import software.coley.recaf.services.text.TextFormatConfig;
import software.coley.recaf.util.io.ByteHeaderUtil;
import software.coley.recaf.util.io.IOUtil;
import software.coley.recaf.util.MemorySegmentUtil;
import software.coley.recaf.util.android.AndroidXmlUtil;
import software.coley.recaf.util.io.ByteHeaderUtil;
import software.coley.recaf.util.io.ByteSource;

import java.io.IOException;
import software.coley.recaf.util.io.IOUtil;

/**
* Basic implementation of the info importer.
Expand All @@ -55,12 +59,12 @@ public BasicInfoImporter(@Nonnull InfoImporterConfig config, @Nonnull TextFormat
@Nonnull
@Override
public Info readInfo(@Nonnull String name, @Nonnull ByteSource source) throws IOException {
byte[] data = source.readAll();
var data = source.readAll();

// Check for Java classes
if (matchesClass(data)) {
if (matchesClass(MemorySegmentUtil.header(data))) {
try {
return readClass(name, data);
return readClass(name, data.toArray(ValueLayout.JAVA_BYTE));
} catch (Throwable t) {
// Invalid class. There are a few possibilities here:
// - The user has disabled patching in their settings and opened an obfuscated file that kills ASM.
Expand All @@ -75,7 +79,7 @@ public Info readInfo(@Nonnull String name, @Nonnull ByteSource source) throws IO
}

// Comparing against known file types.
boolean hasZipMarker = ByteHeaderUtil.matchAtAnyOffset(data, ByteHeaderUtil.ZIP);
boolean hasZipMarker = MemorySegmentUtil.matchAtAnyOffset(data, ByteHeaderUtil.ZIP);
FileInfo info = readAsSpecializedFile(name, data);
if (info != null) {
if (hasZipMarker)
Expand Down Expand Up @@ -124,46 +128,47 @@ public Info readInfo(@Nonnull String name, @Nonnull ByteSource source) throws IO
* or {@code null} if no special case is matched.
*/
@Nullable
private static FileInfo readAsSpecializedFile(@Nonnull String name, byte[] data) {
if (ByteHeaderUtil.match(data, ByteHeaderUtil.DEX)) {
private static FileInfo readAsSpecializedFile(@Nonnull String name, MemorySegment data) {
var header = MemorySegmentUtil.header(data);
if (ByteHeaderUtil.match(header, ByteHeaderUtil.DEX)) {
CodeCodec.readDebug = false; // TODO: Remove this flag when debug parsing is fixed upstream.
return new DexFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (ByteHeaderUtil.match(data, ByteHeaderUtil.MODULES)) {
} else if (ByteHeaderUtil.match(header, ByteHeaderUtil.MODULES)) {
return new ModulesFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (name.toUpperCase().endsWith(".ARSC") &&
ByteHeaderUtil.match(data, ByteHeaderUtil.ARSC)) {
ByteHeaderUtil.match(header, ByteHeaderUtil.ARSC)) {
return new ArscFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (name.toUpperCase().endsWith(".XML") &&
(ByteHeaderUtil.match(data, ByteHeaderUtil.BINARY_XML) || AndroidXmlUtil.hasXmlIndicators(data))) {
(ByteHeaderUtil.match(header, ByteHeaderUtil.BINARY_XML) || AndroidXmlUtil.hasXmlIndicators(header))) {
return new BinaryXmlFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.IMAGE_HEADERS)) {
} else if (ByteHeaderUtil.matchAny(header, ByteHeaderUtil.IMAGE_HEADERS)) {
return new ImageFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.AUDIO_HEADERS)) {
} else if (ByteHeaderUtil.matchAny(header, ByteHeaderUtil.AUDIO_HEADERS)) {
return new AudioFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.VIDEO_HEADERS)) {
} else if (ByteHeaderUtil.matchAny(header, ByteHeaderUtil.VIDEO_HEADERS)) {
return new VideoFileInfoBuilder()
.withRawContent(data)
.withName(name)
.build();
} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.PROGRAM_HEADERS)) {
} else if (ByteHeaderUtil.matchAny(header, ByteHeaderUtil.PROGRAM_HEADERS)) {
return new NativeLibraryFileInfoBuilder()
.withRawContent(data)
.withName(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import java.io.File;
import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileVisitOption;
Expand Down Expand Up @@ -170,7 +171,7 @@ private WorkspaceResource handleSingle(@Nonnull WorkspaceFileResourceBuilder bui
// Check for DEX file format.
if (readInfoAsFile instanceof DexFileInfo) {
String dexName = readInfoAsFile.getName();
AndroidClassBundle dexBundle = DexIOUtil.read(readInfoAsFile.getRawContent());
AndroidClassBundle dexBundle = DexIOUtil.read(readInfoAsFile.getRawContent().toArray(ValueLayout.JAVA_BYTE));
return builder.withAndroidClassBundles(Map.of(dexName, dexBundle))
.build();
}
Expand Down Expand Up @@ -274,7 +275,7 @@ private WorkspaceFileResource handleZip(@Nonnull WorkspaceFileResourceBuilder bu
// Skipping ZIP bombs
if (info.isFile() && info.asFile().isZipFile()) {
ZipFileInfo zipFile = info.asFile().asZipFile();
if (Arrays.equals(zipFile.getRawContent(), zipInfo.getRawContent())) {
if (zipFile.getRawContent().equals(zipInfo.getRawContent())) {
logger.warn("Skip self-extracting ZIP bomb: {}", entryName);
return null;
} else if (Arrays.stream(Thread.currentThread().getStackTrace())
Expand Down
Loading