package com.constellio.model.services.contents.icap; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URI; import java.net.URLEncoder; class IcapClient { private static final String ICAP_SCHEME = "icap"; private static final int CHUNK_BUFFER_MAX_SIZE = 1024 * 4; private static final int DEFAUT_SOCKET_TIMEOUT = 5000; private static final String CR_LF = "\r\n"; private final URI icapServerUrl; private final int socketTimeOut; private Socket socket; private DataOutputStream socketOutputStream; private byte[] chunkBuffer; public IcapClient(final URI icapServerUrl, final Integer socketTimeout) { Validate.notNull(icapServerUrl); Validate.isTrue(ICAP_SCHEME.equals(icapServerUrl.getScheme().toLowerCase())); this.icapServerUrl = icapServerUrl; socketTimeOut = (Integer) ObjectUtils.defaultIfNull(socketTimeout, DEFAUT_SOCKET_TIMEOUT); } public IcapResponse getIcapConfigurationsFromServer() throws IOException { try { connect(); sendOptionsMethodRequest(); return parseResponse(); } finally { disconnect(); } } private void connect() throws IOException { if (socket == null) { socket = new Socket(); socket.connect( new InetSocketAddress( icapServerUrl.getHost(), icapServerUrl.getPort()), socketTimeOut); socket.setSoTimeout(socketTimeOut); socketOutputStream = new DataOutputStream(socket.getOutputStream()); chunkBuffer = new byte[CHUNK_BUFFER_MAX_SIZE]; } } private void sendOptionsMethodRequest() throws IOException { socketOutputStream.writeBytes(buildOptionsMethodRequest()); socketOutputStream.flush(); } private String buildOptionsMethodRequest() { return new StringBuilder(). append("OPTIONS ").append(icapServerUrl).append(" ICAP/1.0"). append(CR_LF). append("Host: ").append(icapServerUrl.getHost()). append(CR_LF). append(CR_LF). toString(); } private IcapResponse parseResponse() throws IOException { return IcapResponse.parse(socket.getInputStream()); } private void disconnect() { IOUtils.closeQuietly(socketOutputStream); IOUtils.closeQuietly(socket); socket = null; chunkBuffer = null; } public IcapResponse scanFile(final String filename, final InputStream fileContent, final String clientHostname, final Integer previewLength) throws IOException { Validate.notNull(filename); Validate.notNull(fileContent); IcapResponse response = null; if (fileContent.available() > 0) { try { connect(); sendRespmodMethodRequest(filename, clientHostname, previewLength); response = sendContentPreview(fileContent, previewLength); if (response.isMoreThanPreviewScanNeeded()) { response = sendPreviewRemaingingContent(fileContent); } } finally { disconnect(); } } else { InputStream mockedIcapResponse = null; try { mockedIcapResponse = new ByteArrayInputStream( new StringBuilder("ICAP/1.0 "). append(HttpStatus.SC_NO_CONTENT). append(" OK"). toString(). getBytes()); response = IcapResponse.parse(mockedIcapResponse); } finally { if (mockedIcapResponse != null) { IOUtils.closeQuietly(mockedIcapResponse); } } } return response; } private void sendRespmodMethodRequest(final String filename, final String clientHostname, final Integer previewLength) throws IOException { socketOutputStream.writeBytes(buildRespmodMethodRequest(filename, clientHostname, previewLength)); socketOutputStream.flush(); } private String buildRespmodMethodRequest(final String filename, final String clientHostname, final Integer previewLength) throws IOException { final String encapsulatedFileGetRequest = buildEncapsulatedFileGetRequest(filename, clientHostname); final String encapsulatedFileGetResponse = buildEncapsulatedFileGetResponse(); return buildRespmodMethodRequest(encapsulatedFileGetRequest, encapsulatedFileGetResponse, clientHostname, previewLength); } private String buildEncapsulatedFileGetRequest(final String filename, final String clientHostname) throws IOException { return new StringBuilder(). append("GET http://").append(clientHostname).append("/").append(URLEncoder.encode(filename, "utf-8")).append(" ").append("HTTP/1.1"). append(CR_LF). append("Host: ").append(clientHostname). append(CR_LF). append(CR_LF). toString(); } private String buildEncapsulatedFileGetResponse() { return new StringBuilder(). append("HTTP/1.1 200 OK"). append(CR_LF). append("Transfer-Encoding: chunked"). append(CR_LF). append(CR_LF). toString(); } private String buildRespmodMethodRequest(final String encapsulatedFileGetRequest, final String encapsulatedFileGetResponse, final String clientHostname, final Integer previewLength) { final int encapsulatedFileGetRequestOffset = encapsulatedFileGetRequest.length(); final int encapsulatedFileGetResponseOffset = encapsulatedFileGetRequestOffset + encapsulatedFileGetResponse.length(); final StringBuilder request = new StringBuilder(). append("RESPMOD ").append(icapServerUrl).append(" ICAP/1.0"). append(CR_LF). append("Host: ").append(icapServerUrl.getHost()). append(CR_LF). append("Allow: 204"). append(CR_LF). append("Encapsulated: req-hdr=0 res-hdr=").append(encapsulatedFileGetRequestOffset).append(" res-body=").append(encapsulatedFileGetResponseOffset). append(CR_LF). append("Preview: ").append(previewLength). append(CR_LF). append("User-Agent: Java"). append(CR_LF); if (StringUtils.isNotBlank(clientHostname)) { request.append("X-Client-IP: ").append(clientHostname) .append(CR_LF); } request.append(CR_LF); request.append(encapsulatedFileGetRequest); request.append(encapsulatedFileGetResponse); return request.toString(); } private IcapResponse sendContentPreview(final InputStream fileContent, final Integer previewLength) throws IOException { writeChunk(fileContent, previewLength); sendBodyEnd(); return parseResponse(); } private int writeChunk(final InputStream fileContent, final int size) throws IOException { final int chunkSize = IOUtils.read(fileContent, chunkBuffer, 0, size); writeChunk(chunkBuffer, 0, chunkSize); return chunkSize; } private void writeChunk(final byte[] chunkBuffer, final int offset, final int chunkSize) throws IOException { if (chunkSize > 0) { socketOutputStream.writeBytes(Long.toHexString(chunkSize) + CR_LF); socketOutputStream.write(chunkBuffer, offset, chunkSize); socketOutputStream.writeBytes(CR_LF); } } private void sendBodyEnd() throws IOException { socketOutputStream.writeBytes("0"); socketOutputStream.writeBytes(CR_LF); socketOutputStream.writeBytes(CR_LF); socketOutputStream.flush(); } private IcapResponse sendPreviewRemaingingContent(final InputStream fileContent) throws IOException { int lastSentChunkSize; do { lastSentChunkSize = writeChunk(fileContent, CHUNK_BUFFER_MAX_SIZE); socketOutputStream.flush(); } while (lastSentChunkSize > 0); sendBodyEnd(); return parseResponse(); } }