private WorkspaceFileResource handleZip(WorkspaceFileResourceBuilder builder, ZipFileInfo zipInfo, ByteSource source) throws IOException {
		logger.info("Reading input from ZIP container '{}'", zipInfo.getName());
		builder.withFileInfo(zipInfo);
		BasicJvmClassBundle classes = new BasicJvmClassBundle();
		BasicFileBundle files = new BasicFileBundle();
		Map<String, AndroidClassBundle> androidClassBundles = new HashMap<>();
		NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles = new TreeMap<>();
		Map<String, WorkspaceFileResource> embeddedResources = new HashMap<>();

		// Read ZIP
		boolean isAndroid = zipInfo.getName().toLowerCase().endsWith(".apk");
		ZipArchive archive = config.mapping().apply(source.readAll());

		// Sanity check, if there's data at the head of the file AND its otherwise empty its probably junk.
		MemorySegment prefixData = archive.getPrefixData();
		if (prefixData != null && archive.getEnd() != null && archive.getParts().size() == 1) {
			// We'll throw as the caller should catch this case and handle it based on their needs.
			throw new IOException("Content matched ZIP header but had no file entries");
		}

		// Record prefix data to attribute held by the zip file info.
		if (prefixData != null) {
			ZipPrefixDataProperty.set(zipInfo, MemorySegmentUtil.toByteArray(prefixData));
		}

		// Build model from the contained files in the ZIP
		archive.getLocalFiles().forEach(header -> {
			LocalFileHeaderSource headerSource = new LocalFileHeaderSource(header, isAndroid);
			String entryName = header.getFileNameAsString();

			// Skip directories. There is no such thing as a 'directory' entry in ZIP files.
			// The only thing we can say is that if it ends with a '/' and has no data associated with it,
			// then it is probably a directory.
			if (entryName.endsWith("/") && Unchecked.getOr(headerSource::isEmpty, false))
				return;

			// Read the value of the entry to figure out how to handle adding it to the resource builder.
			Info info;
			try {
				info = infoImporter.readInfo(entryName, headerSource);
			} catch (IOException ex) {
				logger.error("IO error reading ZIP entry '{}' - skipping", entryName, ex);
				return;
			}

			// Record common entry attributes
			ZipCompressionProperty.set(info, header.getCompressionMethod());
			ExtraFieldTime.TimeWrapper extraTimes = ExtraFieldTime.read(header);
			CentralDirectoryFileHeader centralHeader = header.getLinkedDirectoryFileHeader();
			if (centralHeader != null) {
				if (centralHeader.getFileCommentLength() > 0)
					ZipCommentProperty.set(info, centralHeader.getFileCommentAsString());
				if (extraTimes == null)
					extraTimes = ExtraFieldTime.read(centralHeader);
			}
			if (extraTimes != null) {
				ZipCreationTimeProperty.set(info, extraTimes.getCreationMs());
				ZipModificationTimeProperty.set(info, extraTimes.getModifyMs());
				ZipAccessTimeProperty.set(info, extraTimes.getAccessMs());
			}

			// Skipping ZIP bombs
			if (info.isFile() && info.asFile().isZipFile()) {
				ZipFileInfo zipFile = info.asFile().asZipFile();
				if (Arrays.equals(zipFile.getRawContent(), zipInfo.getRawContent())) {
					logger.warn("Skip self-extracting ZIP bomb: {}", entryName);
					return;
				} else if (Arrays.stream(Thread.currentThread().getStackTrace())
						.filter(trace -> trace.getMethodName().equals("handleZip"))
						.count() > MAX_ZIP_DEPTH) {
					logger.warn("Skip extracting embedded ZIP after {} levels: {}", MAX_ZIP_DEPTH, entryName);
					return;
				}
			}

			// Add the info to the appropriate bundle
			addInfo(classes, files, androidClassBundles, versionedJvmClassBundles, embeddedResources,
					headerSource, entryName, info);
		});
		return builder
				.withJvmClassBundle(classes)
				.withAndroidClassBundles(androidClassBundles)
				.withVersionedJvmClassBundles(versionedJvmClassBundles)
				.withFileBundle(files)
				.withEmbeddedResources(embeddedResources)
				.withFileInfo(zipInfo)
				.build();
	}
General read handling for any single-file resource kind. Delegates to others when needed.

@param builder
		Builder to work with.
@param pathName
		Name of input file / content.
@param source
		Access to content / data.

@return Read resource.
recaf-core/src/main/java/software/coley/recaf/services/workspace/io/BasicResourceImporter.java