Index: lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java =================================================================== diff -u -rb3b03858efeef1c37e36993757f56374a9f2b9f3 -r783f965b1bb2251474d01f32a53e4ccb28d5473b --- lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java (.../FileUtil.java) (revision b3b03858efeef1c37e36993757f56374a9f2b9f3) +++ lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java (.../FileUtil.java) (revision 783f965b1bb2251474d01f32a53e4ccb28d5473b) @@ -27,13 +27,16 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.SimpleDateFormat; +import java.util.Collection; import java.util.Date; +import java.util.Map; import java.util.Properties; import javax.mail.internet.MimeUtility; @@ -60,6 +63,10 @@ import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.security.AnyTypePermission; +import xyz.capybara.clamav.ClamavClient; +import xyz.capybara.clamav.ClamavException; +import xyz.capybara.clamav.commands.scan.result.ScanResult; + /** * General File Utilities */ @@ -640,7 +647,7 @@ ((Configurable) uuidGen).configure(StringType.INSTANCE, new Properties(), null); // Serializable generate(SharedSessionContractImplementor session, Object object) - + // lowercase to resolve OS issues return ((String) uuidGen.generate(null, null)).toLowerCase(); } @@ -847,4 +854,33 @@ return null; } } + + /** + * Checks the given input stream for viruses. Uses ClamAV client. + */ + public static boolean isVirusFree(InputStream inputStream) throws IOException { + // get URL when ClamAV server listens + String host = "localhost"; + int port = 3310; + try { + // set up th client + ClamavClient client = new ClamavClient(host, port); + // check if client can connect; if not, it throws ClamavException + client.ping(); + // try scanning + ScanResult result = client.scan(inputStream); + if (result instanceof ScanResult.OK) { + if (log.isDebugEnabled()) { + log.debug("File scan completed successfully"); + } + return true; + } + // if the result is not OK, write out viruses which have been found + Map> viruses = ((ScanResult.VirusFound) result).getFoundViruses(); + log.info("When uploading a file found viruses: " + viruses.toString()); + } catch (ClamavException e) { + throw new IOException("Could not connect to ClamAV server at " + host + ":" + port, e); + } + return false; + } } Index: lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandler.java =================================================================== diff -u -r62aaf160878735888d077bf28fac3c1989bb8fbd -r783f965b1bb2251474d01f32a53e4ccb28d5473b --- lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandler.java (.../ToolContentHandler.java) (revision 62aaf160878735888d077bf28fac3c1989bb8fbd) +++ lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandler.java (.../ToolContentHandler.java) (revision 783f965b1bb2251474d01f32a53e4ccb28d5473b) @@ -21,7 +21,6 @@ * **************************************************************** */ - package org.lamsfoundation.lams.contentrepository.client; import java.io.IOException; @@ -64,7 +63,7 @@ * Content Repository's applicationContext.xml. The name must be unique to this project. * * For example: - * + * *
  * 	<bean id="blahToolContentHandler" class="your class name here">
  * 		<property name="repositoryService"> <ref bean="repositoryService"/</property>
@@ -84,7 +83,7 @@
  * 
  • IVersionedNode getFileNode(Long uuid)
    * Gets the file node from the repository. To get the file stream, do getFileNode().getFile() * For example:
    - * + * *
      * NodeKey nodeKey = handler.uploadFile(istream, fileName, fileMimeType);
    * Long uuid = nodeKey.getUuid();
    @@ -111,7 +110,7 @@ private IRepositoryService repositoryService; private ITicket ticket; - + private String repositoryWorkspaceName; private String repositoryUser; private String repositoryId; @@ -125,7 +124,7 @@ } return repositoryWorkspaceName; } - + public void setRepositoryWorkspaceName(String repositoryWorkspaceName) { this.repositoryWorkspaceName = repositoryWorkspaceName; } @@ -135,9 +134,9 @@ log.error( "Accessing ToolContentHandler bean, but repositoryUser has not been defined. Please define this property in ApplicationContext.xml"); } - return repositoryUser; + return repositoryUser; } - + public void setRepositoryUser(String repositoryUser) { this.repositoryUser = repositoryUser; } @@ -147,9 +146,9 @@ log.error( "Accessing ToolContentHandler bean, but repositoryWorkspaceName has not been defined. Please define this property in ApplicationContext.xml"); } - return repositoryId.toCharArray(); + return repositoryId.toCharArray(); } - + public void setRepositoryId(String repositoryId) { this.repositoryId = repositoryId; } @@ -162,7 +161,7 @@ // make sure workspace exists - sometimes it does not get created when creating a ticket cred = new SimpleCredentials(getRepositoryUser(), getRepositoryId()); doLogin = !repositoryService.workspaceExists(cred, getRepositoryWorkspaceName()); - } + } if (doLogin) { if (cred == null) { cred = new SimpleCredentials(getRepositoryUser(), getRepositoryId()); @@ -186,14 +185,14 @@ } } catch (RepositoryCheckedException e2) { - log.warn("Unable to to uploadFile" + fileName + "Repository Exception: " + e2.getMessage() + log.warn("Unable to to uploadFile " + fileName + ". Repository Exception: " + e2.getMessage() + " Retry not possible."); throw e2; } return nodeKey; } - + @Override public NodeKey updateFile(Long uuid, InputStream stream, String fileName, String mimeType) throws RepositoryCheckedException, InvalidParameterException, RepositoryCheckedException { @@ -229,7 +228,7 @@ /** * Save a directory of files in the content repository. - * + * * @param ticket * ticket issued on login. Identifies tool and workspace - mandatory * @param dirPath @@ -387,7 +386,7 @@ * out.flush(); * return file; * } - * + * * protected byte[] getBytes(File file) throws FileNotFoundException, Exception { * byte[] byteArray = new byte[(int) file.length()]; * FileInputStream stream = new FileInputStream(file); Index: lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/service/NodeFactory.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r783f965b1bb2251474d01f32a53e4ccb28d5473b --- lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/service/NodeFactory.java (.../NodeFactory.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/service/NodeFactory.java (.../NodeFactory.java) (revision 783f965b1bb2251474d01f32a53e4ccb28d5473b) @@ -76,7 +76,7 @@ @Override public SimpleVersionedNode createFileNode(CrWorkspace workspace, SimpleVersionedNode parentNode, String relPath, InputStream istream, String filename, String mimeType, String versionDescription, Integer userId) - throws org.lamsfoundation.lams.contentrepository.exception.InvalidParameterException { + throws InvalidParameterException { SimpleVersionedNode initialNodeVersion = createBasicNode(NodeType.FILENODE, workspace, parentNode, relPath, versionDescription, userId); Index: lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/service/SimpleVersionedNode.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r783f965b1bb2251474d01f32a53e4ccb28d5473b --- lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/service/SimpleVersionedNode.java (.../SimpleVersionedNode.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/service/SimpleVersionedNode.java (.../SimpleVersionedNode.java) (revision 783f965b1bb2251474d01f32a53e4ccb28d5473b) @@ -21,7 +21,6 @@ * **************************************************************** */ - package org.lamsfoundation.lams.contentrepository.service; import java.io.File; @@ -148,7 +147,7 @@ /* * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#setProperty(java.lang.String, java.lang.String, * int) */ @@ -160,7 +159,7 @@ /* * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#setProperty(java.lang.String, java.lang.String) */ @Override @@ -171,7 +170,7 @@ /* * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#setProperty(java.lang.String, boolean) */ @Override @@ -182,7 +181,7 @@ /* * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#setProperty(java.lang.String, double) */ @Override @@ -193,7 +192,7 @@ /* * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#setProperty(java.lang.String, long) */ @Override @@ -204,7 +203,7 @@ /* * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#setProperty(java.lang.String, java.util.Calendar) */ @Override @@ -271,7 +270,7 @@ /** * (non-Javadoc) - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#isNodeType(java.lang.String) */ @Override @@ -284,7 +283,7 @@ * Get the history for this node. Quite intensive operation * as it has to build all the data structures. Can't be easily * cached. - * + * * @return SortedSet of IVersionDetail objects, ordered by version */ @Override @@ -380,7 +379,7 @@ * of the caller to close the stream. Note: this should only be * called once the node is saved - do not call it directly after * setting the file stream - * + * * If the node is a package node, it will get the input stream * of the first file. */ @@ -416,7 +415,7 @@ /** * Set the file, passed in as an inputstream. The stream will be closed * when the file is saved. Only nodes of type FILENODE can have a file! - * + * * @param iStream * mandatory * @param filename @@ -444,6 +443,15 @@ throw new InvalidParameterException("Filename is required."); } + try { + boolean isVirusFree = FileUtil.isVirusFree(iStream); + if (!isVirusFree) { + throw new InvalidParameterException("File contains a virus: " + filename); + } + } catch (IOException e) { + throw new InvalidParameterException("Could not scan file: " + filename, e); + } + /* Validation passed, set up the file details */ this.filePath = null; // will be set when the stream is written out this.newIStream = iStream; @@ -478,7 +486,7 @@ * we could just be doing a setProperty save, in which case the file * will already exist. *
  • Package nodes must not have a file, must have a INITIALPATH property - * + * * @throws ValidationException * if problems exist. */ @@ -541,19 +549,19 @@ * Save the changes to this node. This method must be called when saving a file * or package node for the first time - it does both the database and the file * saves. - * + * * If it is a file node, then it writes out the db changes and then saves * the file. - * + * * If is is a package node, then it writes out the db changes for all the nodes, * then saves all the file. Why do it this way - we want to do all the file * changes at the end as they cannot be rolled back if there is a db error. - * + * * This method only works as we know that we have two levels of nodes - the * childNodes can't have their own childNodes. If this is no longer the case, * this method and copy() will need to be changed. - * * + * * TODO This needs a lot of testing */ protected Long save() throws ValidationException, FileException { @@ -615,7 +623,8 @@ : element.getFilePath(); } else { failedDeleted = failedDeleted != null - ? failedDeleted + File.pathSeparator + element.getFilePath() : element.getFilePath(); + ? failedDeleted + File.pathSeparator + element.getFilePath() + : element.getFilePath(); } } String msg = "Result of rolling back file changes:"; @@ -667,7 +676,7 @@ /** * Write the file out (if one exists). Sets the private attribute filePath. - * + * * @return the path to which the file was written */ private void writeFile() throws FileException { @@ -695,7 +704,7 @@ /** * Another case for the factory? - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#getNode(String relPath) */ @Override @@ -719,7 +728,7 @@ /** * If no nodes are found, returns an empty set. - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNode#getChildNodes() */ @Override @@ -782,13 +791,13 @@ /** * Delete all versions of this node, returning the number of nodes * deleted. If it is a package node, all child nodes will be deleted. - * + * * @see org.lamsfoundation.lams.contentrepository.IVersionedNodeAdmin#deleteNode() */ @Override public List deleteNode() { - // first make a list of all the versions to delete. + // first make a list of all the versions to delete. // don't iterate over the set, deleting as we go so that // we can't run into any issues trying to access something // that is deleted or belongs to an iterator. @@ -822,7 +831,7 @@ // doing file system changes if the db would fail later. deleteVersionFromDB(nodeKeysDeleted); - // now delete the files. If it fails due to the file not being found, then + // now delete the files. If it fails due to the file not being found, then // that's fine. ArrayList failedList = new ArrayList(); Iterator iter = nodeKeysDeleted.iterator(); @@ -890,7 +899,7 @@ /** * Process files in the package. Create a List of file nodes but do not persist * the nodes. - * + * * @param dirPath: * the directory from which to get files. Mandatory. * @param packageNode: @@ -1009,11 +1018,11 @@ /** * Copy the supplied node/version to a new node. Does not copy the history * of the node. Copies any child nodes of the current version. All files are duplicated. - * + * * This method only works as we know that we have two levels of nodes - the * childNodes can't have their own childNodes. If this is no longer the case, * this method and SimpleVersionedNode.save() will need to be changed. - * + * * @throws FileException * will occur if there is a problem reading a file from the repository * @throws InvalidParameterException