package com.constellio.model.services.contents.icap;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IcapResponse {
static class Builder {
private int httpStatusCode;
private boolean timedout;
private final Map<String, String> headers = new TreeMap<>();
public Builder statusCode(final int httpStatusCode) {
this.httpStatusCode = httpStatusCode;
return this;
}
public Builder timedout(final boolean timedout) {
this.timedout = timedout;
return this;
}
public Builder header(final String nom, final String valeur) {
this.headers.put(nom, valeur);
return this;
}
public IcapResponse build() {
return new IcapResponse(this);
}
}
private static final String ICAP_HEADER_X_VIRUS_NAME = "X-Virus-Name";
private static final String ICAP_HEADER_X_VIRUS_ID = "X-Virus-ID";
private static final String ICAP_HEADER_X_BLOCK_REASON = "X-Block-Reason";
private static final String ICAP_HEADER_X_WWBLOCK_RESULT = "X-WWBlockResult";
private static final String ICAP_HEADER_PREVIEW = "Preview";
private static final String ICAP_HEADER_ENCAPSULATED = "Encapsulated";
private static final String ICAP_HEADER_SEPARATOR = ":";
private static final Pattern STATUS_CODE_PATTERN = Pattern.compile("^ICAP/\\d+\\.\\d+ (\\d+) .*$");
public static IcapResponse parse(final InputStream inputStream)
throws IOException {
Validate.notNull(inputStream);
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final Builder builder = new Builder();
try {
parseStatutCode(bufferedReader, builder);
parseHeaders(bufferedReader, builder);
if (hasEncapsuledHeader(builder)) {
parseEncapsulatedSection(bufferedReader, builder);
}
} catch (final SocketTimeoutException e) {
builder.timedout(true);
}
return builder.build();
}
private static void parseStatutCode(final BufferedReader bufferedReader, final Builder builder)
throws IOException {
final Matcher matcher = STATUS_CODE_PATTERN.matcher(bufferedReader.readLine());
if (matcher.matches()) {
builder.statusCode(Integer.parseInt(matcher.group(1)));
}
}
private static void parseHeaders(final BufferedReader bufferedReader, final Builder builder)
throws IOException {
String line = bufferedReader.readLine();
while (StringUtils.isNotEmpty(line)) {
final String[] header = line.split(ICAP_HEADER_SEPARATOR);
builder.header(header[0].trim(), header[1].trim());
line = bufferedReader.readLine();
}
}
private static boolean hasEncapsuledHeader(final Builder builder) {
return builder.headers.containsKey(ICAP_HEADER_ENCAPSULATED);
}
private static void parseEncapsulatedSection(final BufferedReader bufferedReader, final Builder builder)
throws IOException {
if (hasEncapsulatedRequestHeader(builder)) {
parseEncapsulatedHeader(bufferedReader, builder);
}
if (hasEncapsulatedResponseHeader(builder)) {
parseEncapsulatedHeader(bufferedReader, builder);
}
if (hasEncapsulatedBody(builder)) {
parseEncapsulatedBody(bufferedReader, builder);
}
}
private static boolean hasEncapsulatedRequestHeader(final Builder builder) {
return builder.headers.get(ICAP_HEADER_ENCAPSULATED).contains("req-hdr");
}
private static void parseEncapsulatedHeader(final BufferedReader bufferedReader, final Builder builder)
throws IOException {
String ligne = bufferedReader.readLine();
while (StringUtils.isNotEmpty(ligne)) {
ligne = bufferedReader.readLine();
}
}
private static boolean hasEncapsulatedResponseHeader(final Builder builder) {
return builder.headers.get(ICAP_HEADER_ENCAPSULATED).contains("res-hdr");
}
private static boolean hasEncapsulatedBody(final Builder builder) {
return !builder.headers.get(ICAP_HEADER_ENCAPSULATED).contains("null-body");
}
private static void parseEncapsulatedBody(final BufferedReader bufferedReader, final Builder builder)
throws IOException {
String ligne = bufferedReader.readLine();
while (StringUtils.isNotEmpty(ligne)) {
ligne = bufferedReader.readLine();
}
}
/**
* @see <a href="https://tools.ietf.org/html/rfc3507#section-4.3.3">rfc3507#section-4.3.3</a>
*/
private final int httpStatusCode;
private final boolean timedout;
private final Map<String, String> headers;
private IcapResponse(final Builder builder) {
httpStatusCode = builder.httpStatusCode;
timedout = builder.timedout;
headers = Collections.unmodifiableMap(new TreeMap<>(builder.headers));
}
public boolean isScanTimedout() {
return timedout;
}
public Integer getPreviewLength() {
try {
return Integer.valueOf(headers.get(ICAP_HEADER_PREVIEW));
} catch (final NumberFormatException e) {
return null;
}
}
public boolean isNoThreatFound() {
return HttpStatus.SC_NO_CONTENT == httpStatusCode;
}
boolean isMoreThanPreviewScanNeeded() {
return HttpStatus.SC_CONTINUE == httpStatusCode;
}
public String getThreatDescription() {
final StringBuilder description = new StringBuilder();
if (headers.containsKey(ICAP_HEADER_X_VIRUS_ID)) {
description.append("Virus " + headers.get(ICAP_HEADER_X_VIRUS_ID));
} else if (headers.containsKey(ICAP_HEADER_X_VIRUS_NAME)) {
description.append("Virus " + headers.get(ICAP_HEADER_X_VIRUS_NAME));
}
if (headers.containsKey(ICAP_HEADER_X_BLOCK_REASON)) {
description.append(headers.get(ICAP_HEADER_X_BLOCK_REASON) + " - " + headers.get(ICAP_HEADER_X_WWBLOCK_RESULT));
}
return description.toString();
}
}