static ReceiveResult receiveZipStream(final InputStream inputStream,
                                          final AttributeMap attributeMap,
                                          final Path zipFilePath) throws IOException {
        LOGGER.debug("receiveZipStream() - START zipFilePath: {}", zipFilePath);
        final DurationTimer timer = LogUtil.startTimerIfDebugEnabled(LOGGER);
        final String defaultFeedName = attributeMap.get(StandardHeaderArguments.FEED);
        final String defaultTypeName = attributeMap.get(StandardHeaderArguments.TYPE);
        final FeedKeyInterner feedKeyInterner = FeedKey.createInterner();
        final FeedKey defaultFeedKey = feedKeyInterner.intern(defaultFeedName, defaultTypeName);

        final Map<String, ZipEntryGroup> baseNameToGroupMap = new HashMap<>();
        final ProxyZipValidator validator = new ProxyZipValidator();
        final List<Entry> dataEntries = new ArrayList<>();

        // Create a .zip.staging file for the inputStream to be written to. We can then
        // copy what we want out of that zip into a new zip at zipFilePath.
        // Don't use a temp dir as these files may be very big, so just make it a sibling.
        final Path stagingZipFile = zipFilePath.resolveSibling(zipFilePath.getFileName() + ".staging");
        final long receivedBytes;
        try {
            // Write the stream to disk, because reading the stream as a ZipArchiveInputStream is risky
            // as it can't read the central directory at the end of the stream, so it doesn't know which
            // entries are actually valid and doesn't know the uncompressed sizes.
            receivedBytes = writeStreamToFile(inputStream, stagingZipFile);

            // Clone the zip with added/updated meta entries
            cloneZipFileWithUpdatedMeta(
                    attributeMap,
                    defaultFeedKey,
                    feedKeyInterner,
                    baseNameToGroupMap,
                    validator,
                    dataEntries,
                    stagingZipFile,
                    zipFilePath);
        } finally {
            Files.deleteIfExists(stagingZipFile);
        }

        // TODO : Worry about memory usage here storing potentially 1000's of data entries and groups.
        // Now look at the entries and see if we can match them to meta.
        final List<ZipEntryGroup> entryList = new ArrayList<>();
        for (final ZipEntryGroup.Entry dataEntry : dataEntries) {
            // We only care about data and any other content used to support it.
            final FileName fileName = FileName.parse(dataEntry.getName());

            // First try the full name of the data file as if it were a base name.
            ZipEntryGroup zipEntryGroup = baseNameToGroupMap.get(fileName.getFullName());
            if (zipEntryGroup == null) {
                // If we didn't get it then try the base name.
                zipEntryGroup = baseNameToGroupMap.get(fileName.getBaseName());
            }

            if (zipEntryGroup == null) {
                // If we can't find a matching group then create a new one.
                zipEntryGroup = new ZipEntryGroup(defaultFeedKey);
                zipEntryGroup.setDataEntry(dataEntry);
                entryList.add(zipEntryGroup);

            } else {
                if (zipEntryGroup.getDataEntry() != null) {
                    // This shouldn't really happen as it means we found meta that could be for more than
                    // one data entry.
                    LOGGER.warn(() -> "Meta for multiple data entries found");
                    // It might not be correct, but we will cope with this by duplicating the meta etc.
                    // associated with the data.
                    zipEntryGroup = new ZipEntryGroup(
                            zipEntryGroup.getFeedKey(),
                            zipEntryGroup.getManifestEntry(),
                            zipEntryGroup.getMetaEntry(),
                            zipEntryGroup.getContextEntry(),
                            dataEntry);
                    entryList.add(zipEntryGroup);
                } else {
                    zipEntryGroup.setDataEntry(dataEntry);
                    entryList.add(zipEntryGroup);
                }
            }
        }

        // Make sure we don't have any hanging meta etc.
        baseNameToGroupMap.values()
                .stream()
                .filter(group -> Objects.isNull(group.getDataEntry()))
                .forEach(group -> {
                    if (group.getManifestEntry() != null) {
                        LOGGER.warn(() -> "Unused manifest: " + group.getManifestEntry());
                    }
                    if (group.getMetaEntry() != null) {
                        LOGGER.warn(() -> "Unused meta: " + group.getMetaEntry());
                    }
                    if (group.getContextEntry() != null) {
                        LOGGER.warn(() -> "Unused context: " + group.getContextEntry());
                    }
                });

        // Split the groups by feed key.
        final Map<FeedKey, List<ZipEntryGroup>> feedGroups = entryList.stream()
                .collect(Collectors.groupingBy(ZipEntryGroup::getFeedKey));

        // We might want to know what was wrong with the received data.
        if (!validator.isValid()) {
            LOGGER.debug(validator.getErrorMessage());
        }

        LOGGER.debug(() -> LogUtil.message(
                "receiveZipStream() - FINISH defaultFeedName: '{}', defaultTypeName: '{}', zipFilePath: {}, " +
                "feedKey count: {}, total entry count: {}, duration: {}",
                defaultFeedName,
                defaultTypeName,
                zipFilePath,
                feedGroups.size(),
                LogUtil.swallowExceptions(() -> feedGroups.values().stream().mapToInt(List::size).sum())
                        .orElse(-1),
                timer));

        return new ReceiveResult(feedGroups, receivedBytes, validator.isValid());
    }
Static and pkg private to aid testing
stroom-proxy/stroom-proxy-app/src/main/java/stroom/proxy/app/handler/ZipReceiver.java