diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/virus/ERXClamAvVirusScanner.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/virus/ERXClamAvVirusScanner.java
new file mode 100644
index 00000000000..d633c68ee9f
--- /dev/null
+++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/virus/ERXClamAvVirusScanner.java
@@ -0,0 +1,83 @@
+package er.extensions.virus;
+
+import java.io.File;
+import java.io.InputStream;
+
+import org.apache.log4j.Logger;
+
+import er.extensions.foundation.ERXFileUtilities;
+import er.extensions.foundation.ERXProperties;
+
+/**
+ * Virus scanner that uses ClamAV to check files and streams. Be sure that
+ * ClamAV is installed on the system.
+ *
+ * @property er.extensions.virus.ClamAvVirusScanner set this property to
+ * the path of the clamscan
executable. Defaults to
+ * /usr/bin/clamscan
+ *
+ * @author darkv
+ */
+public class ERXClamAvVirusScanner extends ERXVirusScanner {
+ private static final Logger log = Logger.getLogger(ERXClamAvVirusScanner.class);
+ private static final String clamscan = ERXProperties.stringForKeyWithDefault(
+ "com.nureg.extensions.virus.ClamAvVirusScanner", "/usr/bin/clamscan");
+ private int status = -1;
+
+ public ERXClamAvVirusScanner() {
+ super();
+ }
+
+ public ERXClamAvVirusScanner(File file) {
+ scan(file);
+ }
+
+ public ERXClamAvVirusScanner(InputStream inputStream) {
+ scan(inputStream);
+ }
+
+ @Override
+ public void scan(File file) {
+ status = -1;
+ if (file == null || !file.exists()) {
+ return;
+ }
+ try {
+ Process process = new ProcessBuilder(clamscan, file.getAbsolutePath()).start();
+ status = process.waitFor();
+
+ if (status > 1) {
+ log.warn("Unexpected return code from ClamAV");
+ }
+ } catch (Exception e) {
+ log.error(e, e);
+ }
+ }
+
+ @Override
+ public void scan(InputStream inputStream) {
+ status = -1;
+ if (inputStream == null) {
+ return;
+ }
+ try {
+ Process process = new ProcessBuilder(clamscan, "-").start();
+ ERXFileUtilities.writeInputStreamToOutputStream(inputStream, process.getOutputStream());
+ status = process.waitFor();
+
+ if (status > 1) {
+ log.warn("Unexpected return code from ClamAV");
+ }
+ } catch (Exception e) {
+ log.error(e, e);
+ }
+ }
+
+ @Override
+ public boolean isOk() {
+ if (status == -1) {
+ throw new IllegalStateException("No file nor stream was scanned yet!");
+ }
+ return status == 0 ? true : false;
+ }
+}
diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/virus/ERXVirusScanner.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/virus/ERXVirusScanner.java
new file mode 100644
index 00000000000..221d7bbf263
--- /dev/null
+++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/virus/ERXVirusScanner.java
@@ -0,0 +1,37 @@
+package er.extensions.virus;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * Abstract virus scanner to check files and streams.
+ *
+ * @author darkv
+ */
+public abstract class ERXVirusScanner {
+ /**
+ * Virus scanner should scan the given file. Check {@link #isOk()} if file
+ * passed the check.
+ *
+ * @param file
+ * the file to scan
+ */
+ public abstract void scan(File file);
+
+ /**
+ * Virus scanner should scan the given input stream. Check {@link #isOk()}
+ * if file passed the check.
+ *
+ * @param inputStream
+ * the input stream to scan
+ */
+ public abstract void scan(InputStream inputStream);
+
+ /**
+ * Access the result of the previous scan. If no scan has been made yet a
+ * runtime exception will be thrown.
+ *
+ * @return true
if file/stream is virus free
+ */
+ public abstract boolean isOk();
+}