// Path: api/src/main/java/xyz/docbleach/api/BleachSession.java
// public class BleachSession implements Serializable {
// 
//   private static final Logger LOGGER = LoggerFactory.getLogger(BleachSession.class);
//   private static final int MAX_ONGOING_TASKS = 10;
//   private final transient Bleach bleach;
//   private final Collection<Threat> threats = new ArrayList<>();
//   /**
//    * Counts ongoing tasks, to prevent fork bombs when handling zip archives for instance)
//    */
//   private int ongoingTasks = 0;
// 
//   public BleachSession(Bleach bleach) {
//     this.bleach = bleach;
//   }
// 
//   /**
//    * The BleachSession is able to record threats encountered by the bleach.
//    *
//    * @param threat The removed threat object, containing the threat type and more information
//    */
//   public void recordThreat(Threat threat) {
//     if (threat == null) {
//       return;
//     }
//     LOGGER.trace("Threat recorded: " + threat);
//     threats.add(threat);
//   }
// 
//   public Collection<Threat> getThreats() {
//     return threats;
//   }
// 
//   public int threatCount() {
//     return threats.size();
//   }
// 
//   /**
//    * @return The bleach used in this session
//    */
//   public Bleach getBleach() {
//     return bleach;
//   }
// 
//   public void sanitize(InputStream is, OutputStream os) throws BleachException {
//     try {
//       if (ongoingTasks++ >= MAX_ONGOING_TASKS) {
//         throw new RecursionBleachException(ongoingTasks);
//       }
//       if (!bleach.handlesMagic(is)) {
//         return;
//       }
//       bleach.sanitize(is, os, this);
//     } finally {
//       ongoingTasks--;
//     }
//   }
// }
// 
// Path: api/src/main/java/xyz/docbleach/api/exception/BleachException.java
// public class BleachException extends Exception {
// 
//   public BleachException(Throwable e) {
//     super(e);
//   }
// 
//   public BleachException(String message) {
//     super(message);
//   }
// }
// 
// Path: api/src/main/java/xyz/docbleach/api/util/CloseShieldInputStream.java
// public class CloseShieldInputStream extends FilterInputStream {
// 
//   public CloseShieldInputStream(InputStream inStream) {
//     super(inStream);
//   }
// 
//   @Override
//   public void close() throws IOException {
//     // no-action
//   }
// 
//   public void _close() throws IOException {
//     super.close();
//   }
// }
// 
// Path: api/src/main/java/xyz/docbleach/api/util/StreamUtils.java
// public class StreamUtils {
// 
//   private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtils.class);
// 
//   private StreamUtils() {
//     throw new IllegalAccessError("Utility class");
//   }
// 
//   public static void copy(InputStream is, OutputStream os) throws IOException {
//     byte[] buffer = new byte[100];
//     int len;
//     while ((len = is.read(buffer)) != -1) {
//       os.write(buffer, 0, len);
//     }
//   }
// 
//   public static boolean hasHeader(InputStream stream, byte[] header) {
//     byte[] fileMagic = new byte[header.length];
//     int length;
// 
//     stream.mark(header.length);
// 
//     try {
//       length = stream.read(fileMagic);
// 
//       if (stream instanceof PushbackInputStream) {
//         PushbackInputStream pin = (PushbackInputStream) stream;
//         pin.unread(fileMagic, 0, length);
//       } else {
//         stream.reset();
//       }
//     } catch (IOException e) {
//       LOGGER.warn("An exception occured", e);
//       return false;
//     }
// 
//     return length == header.length && Arrays.equals(fileMagic, header);
//   }
// }


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.docbleach.api.BleachSession;
import xyz.docbleach.api.exception.BleachException;
import xyz.docbleach.api.util.CloseShieldInputStream;
import xyz.docbleach.api.util.StreamUtils;
package xyz.docbleach.api.bleach;


public class CompositeBleach implements Bleach {

  private static final Logger LOGGER = LoggerFactory.getLogger(CompositeBleach.class);
  private final Collection<Bleach> bleaches = new ArrayList<>();
  private final String name;

  public CompositeBleach(Bleach... bleaches) {
    Collections.addAll(this.bleaches, bleaches);

    name = buildName(bleaches);
  }

  private String buildName(Bleach[] bleaches) {
    StringBuilder myName = new StringBuilder("CompositeBleach: ");
    for (Bleach b : bleaches) {
      myName.append(b.getName()).append(" ");
    }
    return myName.toString().trim();
  }

  @Override
  public boolean handlesMagic(InputStream stream) {
    return bleaches.stream().anyMatch(bleach -> bleach.handlesMagic(stream));
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public void sanitize(InputStream inputStream, OutputStream outputStream, BleachSession session)
// Path: api/src/main/java/xyz/docbleach/api/BleachSession.java
// public class BleachSession implements Serializable {
// 
//   private static final Logger LOGGER = LoggerFactory.getLogger(BleachSession.class);
//   private static final int MAX_ONGOING_TASKS = 10;
//   private final transient Bleach bleach;
//   private final Collection<Threat> threats = new ArrayList<>();
//   /**
//    * Counts ongoing tasks, to prevent fork bombs when handling zip archives for instance)
//    */
//   private int ongoingTasks = 0;
// 
//   public BleachSession(Bleach bleach) {
//     this.bleach = bleach;
//   }
// 
//   /**
//    * The BleachSession is able to record threats encountered by the bleach.
//    *
//    * @param threat The removed threat object, containing the threat type and more information
//    */
//   public void recordThreat(Threat threat) {
//     if (threat == null) {
//       return;
//     }
//     LOGGER.trace("Threat recorded: " + threat);
//     threats.add(threat);
//   }
// 
//   public Collection<Threat> getThreats() {
//     return threats;
//   }
// 
//   public int threatCount() {
//     return threats.size();
//   }
// 
//   /**
//    * @return The bleach used in this session
//    */
//   public Bleach getBleach() {
//     return bleach;
//   }
// 
//   public void sanitize(InputStream is, OutputStream os) throws BleachException {
//     try {
//       if (ongoingTasks++ >= MAX_ONGOING_TASKS) {
//         throw new RecursionBleachException(ongoingTasks);
//       }
//       if (!bleach.handlesMagic(is)) {
//         return;
//       }
//       bleach.sanitize(is, os, this);
//     } finally {
//       ongoingTasks--;
//     }
//   }
// }
// 
// Path: api/src/main/java/xyz/docbleach/api/exception/BleachException.java
// public class BleachException extends Exception {
// 
//   public BleachException(Throwable e) {
//     super(e);
//   }
// 
//   public BleachException(String message) {
//     super(message);
//   }
// }
// 
// Path: api/src/main/java/xyz/docbleach/api/util/CloseShieldInputStream.java
// public class CloseShieldInputStream extends FilterInputStream {
// 
//   public CloseShieldInputStream(InputStream inStream) {
//     super(inStream);
//   }
// 
//   @Override
//   public void close() throws IOException {
//     // no-action
//   }
// 
//   public void _close() throws IOException {
//     super.close();
//   }
// }
// 
// Path: api/src/main/java/xyz/docbleach/api/util/StreamUtils.java
// public class StreamUtils {
// 
//   private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtils.class);
// 
//   private StreamUtils() {
//     throw new IllegalAccessError("Utility class");
//   }
// 
//   public static void copy(InputStream is, OutputStream os) throws IOException {
//     byte[] buffer = new byte[100];
//     int len;
//     while ((len = is.read(buffer)) != -1) {
//       os.write(buffer, 0, len);
//     }
//   }
// 
//   public static boolean hasHeader(InputStream stream, byte[] header) {
//     byte[] fileMagic = new byte[header.length];
//     int length;
// 
//     stream.mark(header.length);
// 
//     try {
//       length = stream.read(fileMagic);
// 
//       if (stream instanceof PushbackInputStream) {
//         PushbackInputStream pin = (PushbackInputStream) stream;
//         pin.unread(fileMagic, 0, length);
//       } else {
//         stream.reset();
//       }
//     } catch (IOException e) {
//       LOGGER.warn("An exception occured", e);
//       return false;
//     }
// 
//     return length == header.length && Arrays.equals(fileMagic, header);
//   }
// }


// Path: api/src/main/java/xyz/docbleach/api/bleach/CompositeBleach.java
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.docbleach.api.BleachSession;
import xyz.docbleach.api.exception.BleachException;
import xyz.docbleach.api.util.CloseShieldInputStream;
import xyz.docbleach.api.util.StreamUtils;

package xyz.docbleach.api.bleach;


public class CompositeBleach implements Bleach {

  private static final Logger LOGGER = LoggerFactory.getLogger(CompositeBleach.class);
  private final Collection<Bleach> bleaches = new ArrayList<>();
  private final String name;

  public CompositeBleach(Bleach... bleaches) {
    Collections.addAll(this.bleaches, bleaches);

    name = buildName(bleaches);
  }

  private String buildName(Bleach[] bleaches) {
    StringBuilder myName = new StringBuilder("CompositeBleach: ");
    for (Bleach b : bleaches) {
      myName.append(b.getName()).append(" ");
    }
    return myName.toString().trim();
  }

  @Override
  public boolean handlesMagic(InputStream stream) {
    return bleaches.stream().anyMatch(bleach -> bleach.handlesMagic(stream));
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public void sanitize(InputStream inputStream, OutputStream outputStream, BleachSession session)
      throws BleachException {