Skip to content
Snippets Groups Projects
Commit d91502f5 authored by sidheshenator's avatar sidheshenator
Browse files

extract unknown size file content from archives

parent f40776c3
No related branches found
No related tags found
No related merge requests found
...@@ -37,7 +37,7 @@ public class FileTypeExtensions { ...@@ -37,7 +37,7 @@ public class FileTypeExtensions {
private final static List<String> TEXT_EXTENSIONS = Arrays.asList(".txt", ".rtf", ".log", ".text", ".xml"); //NON-NLS private final static List<String> TEXT_EXTENSIONS = Arrays.asList(".txt", ".rtf", ".log", ".text", ".xml"); //NON-NLS
private final static List<String> WEB_EXTENSIONS = Arrays.asList(".html", ".htm", ".css", ".js", ".php", ".aspx"); //NON-NLS private final static List<String> WEB_EXTENSIONS = Arrays.asList(".html", ".htm", ".css", ".js", ".php", ".aspx"); //NON-NLS
private final static List<String> PDF_EXTENSIONS = Arrays.asList(".pdf"); //NON-NLS private final static List<String> PDF_EXTENSIONS = Arrays.asList(".pdf"); //NON-NLS
private final static List<String> ARCHIVE_EXTENSIONS = Arrays.asList(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".jar", ".cpio", ".ar", ".gz", ".tgz"); //NON-NLS private final static List<String> ARCHIVE_EXTENSIONS = Arrays.asList(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".jar", ".cpio", ".ar", ".gz", ".tgz", ".bz2"); //NON-NLS
public static List<String> getImageExtensions() { public static List<String> getImageExtensions() {
return IMAGE_EXTENSIONS; return IMAGE_EXTENSIONS;
......
...@@ -36,4 +36,6 @@ EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err=Pptx con ...@@ -36,4 +36,6 @@ EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err=Pptx con
EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err=Xls container could not be initialized while reading: {0} EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err=Xls container could not be initialized while reading: {0}
EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err=Xlsx container could not be initialized while reading: {0} EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err=Xlsx container could not be initialized while reading: {0}
EmbeddedFileExtractorIngestModule.ImageExtractor.extractImage.addToDB.exception.msg=Unable to add the derived files to the database. EmbeddedFileExtractorIngestModule.ImageExtractor.extractImage.addToDB.exception.msg=Unable to add the derived files to the database.
EmbeddedFileExtractorIngestModule.ImageExtractor.getOutputFolderPath.exception.msg=Could not get path for image extraction from Abstract File: {0} EmbeddedFileExtractorIngestModule.ImageExtractor.getOutputFolderPath.exception.msg=Could not get path for image extraction from Abstract File: {0}
\ No newline at end of file EmbeddedFileExtractorIngestModule.ImageExtractor.getOutputFolderPath.exception.msg=Could not get path for image extraction from Abstract File: {0}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg=Unable to write content to disk. Not enough space.
\ No newline at end of file
...@@ -24,6 +24,8 @@ ...@@ -24,6 +24,8 @@
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
...@@ -40,7 +42,6 @@ ...@@ -40,7 +42,6 @@
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.casemodule.services.FileManager;
...@@ -98,7 +99,8 @@ private enum SupportedArchiveExtractionFormats { ...@@ -98,7 +99,8 @@ private enum SupportedArchiveExtractionFormats {
GZIP("application/gzip"), GZIP("application/gzip"),
XGZIP("application/x-gzip"), XGZIP("application/x-gzip"),
XBZIP2("application/x-bzip2"), XBZIP2("application/x-bzip2"),
XTAR("application/x-tar"); XTAR("application/x-tar"),
XGTAR("application/x-gtar");
private final String mimeType; private final String mimeType;
...@@ -121,9 +123,9 @@ public String toString() { ...@@ -121,9 +123,9 @@ public String toString() {
logger.log(Level.INFO, "7-Zip-JBinding library was initialized on supported platform: {0}", platform); //NON-NLS logger.log(Level.INFO, "7-Zip-JBinding library was initialized on supported platform: {0}", platform); //NON-NLS
} catch (SevenZipNativeInitializationException e) { } catch (SevenZipNativeInitializationException e) {
logger.log(Level.SEVERE, "Error initializing 7-Zip-JBinding library", e); //NON-NLS logger.log(Level.SEVERE, "Error initializing 7-Zip-JBinding library", e); //NON-NLS
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg", String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg",
EmbeddedFileExtractorModuleFactory.getModuleName()); EmbeddedFileExtractorModuleFactory.getModuleName());
String details = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib", String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
e.getMessage()); e.getMessage());
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
throw new IngestModuleException(e.getMessage()); throw new IngestModuleException(e.getMessage());
...@@ -204,7 +206,7 @@ private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArc ...@@ -204,7 +206,7 @@ private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArc
if (cRatio >= MAX_COMPRESSION_RATIO) { if (cRatio >= MAX_COMPRESSION_RATIO) {
String itemName = archiveFileItem.getPath(); String itemName = archiveFileItem.getPath();
logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS
String msg = NbBundle.getMessage(this.getClass(), String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName); "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
String path; String path;
try { try {
...@@ -212,7 +214,7 @@ private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArc ...@@ -212,7 +214,7 @@ private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArc
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
path = archiveFile.getParentPath() + archiveFile.getName(); path = archiveFile.getParentPath() + archiveFile.getName();
} }
String details = NbBundle.getMessage(this.getClass(), String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path); "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
//MessageNotifyUtil.Notify.error(msg, details); //MessageNotifyUtil.Notify.error(msg, details);
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
...@@ -311,9 +313,9 @@ void unpack(AbstractFile archiveFile) { ...@@ -311,9 +313,9 @@ void unpack(AbstractFile archiveFile) {
if (parentAr == null) { if (parentAr == null) {
parentAr = archiveDepthCountTree.addArchive(null, archiveId); parentAr = archiveDepthCountTree.addArchive(null, archiveId);
} else if (parentAr.getDepth() == MAX_DEPTH) { } else if (parentAr.getDepth() == MAX_DEPTH) {
String msg = NbBundle.getMessage(this.getClass(), String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName()); "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
String details = NbBundle.getMessage(this.getClass(), String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
parentAr.getDepth(), archiveFilePath); parentAr.getDepth(), archiveFilePath);
//MessageNotifyUtil.Notify.error(msg, details); //MessageNotifyUtil.Notify.error(msg, details);
...@@ -328,7 +330,7 @@ void unpack(AbstractFile archiveFile) { ...@@ -328,7 +330,7 @@ void unpack(AbstractFile archiveFile) {
SevenZipContentReadStream stream = null; SevenZipContentReadStream stream = null;
final ProgressHandle progress = ProgressHandleFactory.createHandle( final ProgressHandle progress = ProgressHandleFactory.createHandle(
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName")); NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
int processedItems = 0; int processedItems = 0;
boolean progressStarted = false; boolean progressStarted = false;
...@@ -400,7 +402,7 @@ void unpack(AbstractFile archiveFile) { ...@@ -400,7 +402,7 @@ void unpack(AbstractFile archiveFile) {
pathInArchive = "/" + useName; pathInArchive = "/" + useName;
} }
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg", String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
archiveFilePath, pathInArchive); archiveFilePath, pathInArchive);
logger.log(Level.WARNING, msg); logger.log(Level.WARNING, msg);
...@@ -432,24 +434,19 @@ void unpack(AbstractFile archiveFile) { ...@@ -432,24 +434,19 @@ void unpack(AbstractFile archiveFile) {
fullEncryption = false; fullEncryption = false;
} }
final Long size = item.getSize(); // NOTE: item.getSize() may return null in case of certain
if (size == null) { // archiving formats. Eg: BZ2
// If the size property cannot be determined, out-of-disk-space Long size = item.getSize();
// situations cannot be ascertained.
// Hence skip this file.
logger.log(Level.WARNING, "Size cannot be determined. Skipping file in archive: {0}", pathInArchive); //NON-NLS
continue;
}
//check if unpacking this file will result in out of disk space //check if unpacking this file will result in out of disk space
//this is additional to zip bomb prevention mechanism //this is additional to zip bomb prevention mechanism
if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size > 0) { //if known free space and file not empty if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size != null && size > 0) { //if free space is known and file is not empty.
long newDiskSpace = freeDiskSpace - size; long newDiskSpace = freeDiskSpace - size;
if (newDiskSpace < MIN_FREE_DISK_SPACE) { if (newDiskSpace < MIN_FREE_DISK_SPACE) {
String msg = NbBundle.getMessage(this.getClass(), String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg", "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
archiveFilePath, fileName); archiveFilePath, fileName);
String details = NbBundle.getMessage(this.getClass(), String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details"); "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
//MessageNotifyUtil.Notify.error(msg, details); //MessageNotifyUtil.Notify.error(msg, details);
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
...@@ -501,21 +498,31 @@ void unpack(AbstractFile archiveFile) { ...@@ -501,21 +498,31 @@ void unpack(AbstractFile archiveFile) {
final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000; final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000; final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
//record derived data in unode, to be traversed later after unpacking the archive
unpackedNode.addDerivedInfo(size, !isDir,
0L, createtime, accesstime, modtime, localRelPath);
//unpack locally if a file //unpack locally if a file
SevenZipExtractor.UnpackStream unpackStream = null;
if (!isDir) { if (!isDir) {
SevenZipExtractor.UnpackStream unpackStream = null;
try { try {
unpackStream = new SevenZipExtractor.UnpackStream(localAbsPath); unpackStream = new SevenZipExtractor.UnpackStream(localAbsPath, freeDiskSpace, size == null);
item.extractSlow(unpackStream); item.extractSlow(unpackStream);
} catch (Exception e) { } catch (Exception e) {
//could be something unexpected with this file, move on //could be something unexpected with this file, move on
logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS
} finally { } finally {
if (unpackStream != null) { if (unpackStream != null) {
//record derived data in unode, to be traversed later after unpacking the archive
if (size != null) {
// unpackedNode.bytesWritten will not be set in
// this case. Use 'size' which has been set
// previously.
unpackedNode.addDerivedInfo(size, !isDir,
0L, createtime, accesstime, modtime, localRelPath);
} else {
// since size is unknown, use
// unpackStream.getNumberOfBytesWritten() to get
// the size.
unpackedNode.addDerivedInfo(unpackStream.getNumberOfBytesWritten(), !isDir,
0L, createtime, accesstime, modtime, localRelPath);
}
unpackStream.close(); unpackStream.close();
} }
} }
...@@ -549,9 +556,9 @@ void unpack(AbstractFile archiveFile) { ...@@ -549,9 +556,9 @@ void unpack(AbstractFile archiveFile) {
// print a message if the file is allocated // print a message if the file is allocated
if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) { if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg", String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
archiveFile.getName()); archiveFile.getName());
String details = NbBundle.getMessage(this.getClass(), String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details", "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
archiveFilePath, ex.getMessage()); archiveFilePath, ex.getMessage());
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
...@@ -590,8 +597,8 @@ void unpack(AbstractFile archiveFile) { ...@@ -590,8 +597,8 @@ void unpack(AbstractFile archiveFile) {
logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS
} }
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg"); String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
String details = NbBundle.getMessage(this.getClass(), String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details", "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName()); archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
...@@ -612,8 +619,15 @@ private static class UnpackStream implements ISequentialOutStream { ...@@ -612,8 +619,15 @@ private static class UnpackStream implements ISequentialOutStream {
private OutputStream output; private OutputStream output;
private String localAbsPath; private String localAbsPath;
private long freeDiskSpace;
UnpackStream(String localAbsPath) { private boolean sizeUnknown = false;
private boolean outOfSpace = false;
private long bytesWritten = 0;
UnpackStream(String localAbsPath, long freeDiskSpace, boolean sizeUnknown) {
this.sizeUnknown = sizeUnknown;
this.freeDiskSpace = freeDiskSpace;
this.localAbsPath = localAbsPath;
try { try {
output = new BufferedOutputStream(new FileOutputStream(localAbsPath)); output = new BufferedOutputStream(new FileOutputStream(localAbsPath));
} catch (FileNotFoundException ex) { } catch (FileNotFoundException ex) {
...@@ -622,13 +636,38 @@ private static class UnpackStream implements ISequentialOutStream { ...@@ -622,13 +636,38 @@ private static class UnpackStream implements ISequentialOutStream {
} }
public long getNumberOfBytesWritten() {
return this.bytesWritten;
}
@Override @Override
public int write(byte[] bytes) throws SevenZipException { public int write(byte[] bytes) throws SevenZipException {
try { try {
output.write(bytes); if (!sizeUnknown) {
output.write(bytes);
} else {
// If the content size is unknown, cautiously write to disk.
// Write only if byte array is less than 80% of the current
// free disk space.
if (freeDiskSpace == IngestMonitor.DISK_FREE_SPACE_UNKNOWN || bytes.length < 0.8 * freeDiskSpace) {
output.write(bytes);
// NOTE: this method is called multiple times for a
// single extractSlow() call. Update bytesWritten and
// freeDiskSpace after every write operation.
this.bytesWritten += bytes.length;
this.freeDiskSpace -= bytes.length;
} else {
this.outOfSpace = true;
logger.log(Level.INFO, NbBundle.getMessage(
SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
throw new SevenZipException(
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
}
}
} catch (IOException ex) { } catch (IOException ex) {
throw new SevenZipException( throw new SevenZipException(
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg", NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
localAbsPath), ex); localAbsPath), ex);
} }
return bytes.length; return bytes.length;
...@@ -639,6 +678,9 @@ public void close() { ...@@ -639,6 +678,9 @@ public void close() {
try { try {
output.flush(); output.flush();
output.close(); output.close();
if (this.outOfSpace) {
Files.delete(Paths.get(this.localAbsPath));
}
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
} }
...@@ -774,7 +816,7 @@ private void addDerivedFilesToCaseRec(UnpackedNode node, FileManager fileManager ...@@ -774,7 +816,7 @@ private void addDerivedFilesToCaseRec(UnpackedNode node, FileManager fileManager
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS
throw new TskCoreException( throw new TskCoreException(
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg", NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
fileName), ex); fileName), ex);
} }
...@@ -961,4 +1003,4 @@ int getDepth() { ...@@ -961,4 +1003,4 @@ int getDepth() {
} }
} }
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment