// Path: src/main/java/me/itzg/mccy/services/ZipMiningService.java
// public interface ZipMiningService {
//     /**
//      * Walks a zip file invoking the given handlers and returns a file hash ID.
//      * @param rawInputStream the raw input stream of the zip file
//      * @param handlers an optional list of handler to be invoked
//      * @return the overall file's hash ID
//      * @throws IOException
//      */
//     String interrogate(InputStream rawInputStream,
//                        Optional<List<ZipMiningHandler.Entry>> handlers) throws IOException;
// }
// 
// Path: src/main/java/me/itzg/mccy/types/MccyException.java
// public class MccyException extends Throwable {
//     public MccyException() {
//     }
// 
//     public MccyException(String message) {
//         super(message);
//     }
// 
//     public MccyException(String message, Throwable cause) {
//         super(message, cause);
//     }
// 
//     public MccyException(Throwable cause) {
//         super(cause);
//     }
// }
// 
// Path: src/main/java/me/itzg/mccy/types/ZipMiningHandler.java
// public interface ZipMiningHandler {
//     void handleZipContentFile(String path, InputStream in) throws IOException, MccyException;
// 
//     static ListBuilder listBuilder() {
//         return new ListBuilder();
//     }
// 
//     class Entry {
//         private Pattern path;
//         private ZipMiningHandler handler;
// 
//         public Entry(Pattern path, ZipMiningHandler handler) {
//             this.path = path;
//             this.handler = handler;
//         }
// 
//         public Pattern getPath() {
//             return path;
//         }
// 
//         public ZipMiningHandler getHandler() {
//             return handler;
//         }
//     }
// 
//     class ListBuilder {
// 
//         private List<Entry> entries = new ArrayList<>();
// 
//         public ListBuilder add(String pathRegex, ZipMiningHandler handler) {
//             entries.add(new Entry(Pattern.compile(pathRegex), handler));
//             return this;
//         }
// 
//         public Optional<List<Entry>> build() {
//             return Optional.of(entries);
//         }
// 
//     }
// }


import com.google.common.hash.HashFunction;
import com.google.common.hash.HashingInputStream;
import me.itzg.mccy.services.ZipMiningService;
import me.itzg.mccy.types.MccyException;
import me.itzg.mccy.types.ZipMiningHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
package me.itzg.mccy.services.impl;



/**
 * Mines a given input stream, opens it as a zip stream, and looks for one or more files within the zip
 * calling a handler for each.
 * It also computes the hash of the outer zip file for identification purposes.
 *
 * @author Geoff Bourne
 * @since 0.2
 */
@Service
public class ZipMiningServiceImpl implements ZipMiningService {
    private static Logger LOG = LoggerFactory.getLogger(ZipMiningService.class);

    @Autowired
    private HashFunction fileIdHash;

    @Override public String interrogate(InputStream rawInputStream,
                              Optional<List<ZipMiningHandler.Entry>> handlers) throws IOException {
        final HashingInputStream hashingInputStream = new HashingInputStream(fileIdHash, rawInputStream);

        final ZipInputStream zipInputStream = new ZipInputStream(hashingInputStream);

        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            if (handlers != null) {
                final String name = zipEntry.getName();
                handlers.orElse(Collections.emptyList()).stream()
                        .filter(e -> e.getPath().matcher(name).matches())
                        .forEach(e -> {
                            final ZipMiningHandler handler = e.getHandler();
                            try {
                                handler.handleZipContentFile(name, StreamUtils.nonClosing(zipInputStream));
// Path: src/main/java/me/itzg/mccy/services/ZipMiningService.java
// public interface ZipMiningService {
//     /**
//      * Walks a zip file invoking the given handlers and returns a file hash ID.
//      * @param rawInputStream the raw input stream of the zip file
//      * @param handlers an optional list of handler to be invoked
//      * @return the overall file's hash ID
//      * @throws IOException
//      */
//     String interrogate(InputStream rawInputStream,
//                        Optional<List<ZipMiningHandler.Entry>> handlers) throws IOException;
// }
// 
// Path: src/main/java/me/itzg/mccy/types/MccyException.java
// public class MccyException extends Throwable {
//     public MccyException() {
//     }
// 
//     public MccyException(String message) {
//         super(message);
//     }
// 
//     public MccyException(String message, Throwable cause) {
//         super(message, cause);
//     }
// 
//     public MccyException(Throwable cause) {
//         super(cause);
//     }
// }
// 
// Path: src/main/java/me/itzg/mccy/types/ZipMiningHandler.java
// public interface ZipMiningHandler {
//     void handleZipContentFile(String path, InputStream in) throws IOException, MccyException;
// 
//     static ListBuilder listBuilder() {
//         return new ListBuilder();
//     }
// 
//     class Entry {
//         private Pattern path;
//         private ZipMiningHandler handler;
// 
//         public Entry(Pattern path, ZipMiningHandler handler) {
//             this.path = path;
//             this.handler = handler;
//         }
// 
//         public Pattern getPath() {
//             return path;
//         }
// 
//         public ZipMiningHandler getHandler() {
//             return handler;
//         }
//     }
// 
//     class ListBuilder {
// 
//         private List<Entry> entries = new ArrayList<>();
// 
//         public ListBuilder add(String pathRegex, ZipMiningHandler handler) {
//             entries.add(new Entry(Pattern.compile(pathRegex), handler));
//             return this;
//         }
// 
//         public Optional<List<Entry>> build() {
//             return Optional.of(entries);
//         }
// 
//     }
// }


// Path: src/main/java/me/itzg/mccy/services/impl/ZipMiningServiceImpl.java
import com.google.common.hash.HashFunction;
import com.google.common.hash.HashingInputStream;
import me.itzg.mccy.services.ZipMiningService;
import me.itzg.mccy.types.MccyException;
import me.itzg.mccy.types.ZipMiningHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

package me.itzg.mccy.services.impl;



/**
 * Mines a given input stream, opens it as a zip stream, and looks for one or more files within the zip
 * calling a handler for each.
 * It also computes the hash of the outer zip file for identification purposes.
 *
 * @author Geoff Bourne
 * @since 0.2
 */
@Service
public class ZipMiningServiceImpl implements ZipMiningService {
    private static Logger LOG = LoggerFactory.getLogger(ZipMiningService.class);

    @Autowired
    private HashFunction fileIdHash;

    @Override public String interrogate(InputStream rawInputStream,
                              Optional<List<ZipMiningHandler.Entry>> handlers) throws IOException {
        final HashingInputStream hashingInputStream = new HashingInputStream(fileIdHash, rawInputStream);

        final ZipInputStream zipInputStream = new ZipInputStream(hashingInputStream);

        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            if (handlers != null) {
                final String name = zipEntry.getName();
                handlers.orElse(Collections.emptyList()).stream()
                        .filter(e -> e.getPath().matcher(name).matches())
                        .forEach(e -> {
                            final ZipMiningHandler handler = e.getHandler();
                            try {
                                handler.handleZipContentFile(name, StreamUtils.nonClosing(zipInputStream));
                            } catch (IOException | MccyException e1) {