Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/org/olat/core/commons/modules/bc/FileSelection.java
### 代码内容：
package org.olat.core.commons.modules.bc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.olat.core.gui.UserRequest;
import org.olat.core.util.FileUtils;
import org.olat.core.util.StringHelper;

public class FileSelection {

	/** HTML form identifier */
	public static final String FORM_ID = "paths";

	private List<String> files = new ArrayList<>();
	private String currentContainerRelPath;

	public FileSelection(UserRequest ureq, String currentContainerRelPath) {
		if (currentContainerRelPath.equals("/")) currentContainerRelPath = "";
		this.currentContainerRelPath = currentContainerRelPath;
		parse(ureq);
	}

	public List<String> getFiles() {
		return files;
	}

	/**
	 * Checks if there is at least one file with invalid file name in the selection.
	 * Returns the list with the invalid filenames.
	 *
	 * @return
	 */
	public List<String> getInvalidFileNames() {
		List<String> invalidFileNames = new ArrayList<>();
		List<String> filesList = getFiles();
		for(String fileName:filesList) {
			if(!FileUtils.validateFilename(fileName)) {
				invalidFileNames.add(fileName);
			}
		}
		return invalidFileNames;
	}

	/**
	 * Parse paths and build BriefcasePath wrappers.
	 * @param base
	 * @param ureq
	 */
	private void parse(UserRequest ureq) {
		String[] sFiles = ureq.getHttpReq().getParameterValues(FORM_ID);
		if (sFiles == null || sFiles.length == 0) return;
		files = Arrays.asList(sFiles);
	}

	/**
	 * Render pathset as HTML.
	 *
	 * @return HTML Fragment.
	 */
	public String renderAsHtml() {
		StringBuilder sb = new StringBuilder(255);
		sb.append("
");
		for (String filename:files) {
			sb.append("
")
			  .append(currentContainerRelPath).append("/").append(StringHelper.escapeHtml(filename))
			  .append("
");
		}
		sb.append("
");
		return sb.toString();
	}
}

## 内容2
### 文件路径：
src/main/java/org/olat/core/commons/modules/bc/commands/CmdDelete.java
### 代码内容：
package org.olat.core.commons.modules.bc.commands;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.FileSelection;
import org.olat.core.commons.modules.bc.FolderEvent;
import org.olat.core.commons.modules.bc.components.FolderComponent;
import org.olat.core.commons.services.vfs.VFSVersionModule;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLockApplicationType;
import org.olat.core.util.vfs.VFSLockManager;

public class CmdDelete extends BasicController implements FolderCommand {

	private static int status = FolderCommandStatus.STATUS_SUCCESS;

	private Translator translator;
	private FolderComponent folderComponent;
	private FileSelection fileSelection;

	private DialogBoxController dialogCtr;
	private DialogBoxController lockedFiledCtr;

	private final boolean versionsEnabled;
	private final VFSLockManager lockManager;

	protected CmdDelete(UserRequest ureq, WindowControl wControl) {
		super(ureq, wControl);
		versionsEnabled = CoreSpringFactory.getImpl(VFSVersionModule.class).isEnabled();
		lockManager = CoreSpringFactory.getImpl(VFSLockManager.class);
	}

	@Override
	public Controller execute(FolderComponent fc, UserRequest ureq, WindowControl wContr, Translator trans) {
		this.translator = trans;
		this.folderComponent = fc;
		this.fileSelection = new FileSelection(ureq, fc.getCurrentContainerPath());

		VFSContainer currentContainer = folderComponent.getCurrentContainer();
		List<String> lockedFiles = hasLockedFiles(currentContainer, fileSelection);
		if (lockedFiles.isEmpty()) {
			String msg = trans.translate("del.confirm") + "
" + fileSelection.renderAsHtml() + "
";
			// create dialog controller
			dialogCtr = activateYesNoDialog(ureq, trans.translate("del.header"), msg, dialogCtr);
		} else {
			String msg = FolderCommandHelper.renderLockedMessageAsHtml(trans, lockedFiles);
			List<String> buttonLabels = Collections.singletonList(trans.translate("ok"));
			lockedFiledCtr = activateGenericDialog(ureq, trans.translate("lock.title"), msg, buttonLabels, lockedFiledCtr);
		}
		return this;
	}

	public List<String> hasLockedFiles(VFSContainer container, FileSelection selection) {
		List<String> lockedFiles = new ArrayList<>();
		for (String file : selection.getFiles()) {
			VFSItem item = container.resolve(file);
			if (lockManager.isLockedForMe(item, getIdentity(), VFSLockApplicationType.vfs, null)) {
				lockedFiles.add(file);
			}
		}
		return lockedFiles;
	}

	@Override
	public int getStatus() {
		return status;
	}

	@Override
	public boolean runsModal() {
		// this controller has its own modal dialog box
		return true;
	}

	@Override
	public String getModalTitle() {
		return null;
	}

	public FileSelection getFileSelection() {
		return fileSelection;
	}

	@Override
	public void event(UserRequest ureq, Controller source, Event event) {
		if (source == dialogCtr) {
			if (DialogBoxUIFactory.isYesEvent(event)) {
				// do delete
				VFSContainer currentContainer = folderComponent.getCurrentContainer();
				List<String> files = fileSelection.getFiles();
				if (files.isEmpty()) {
					// sometimes, browser sends empty form data...
					getWindowControl().setError(translator.translate("failed"));
					status = FolderCommandStatus.STATUS_FAILED;
					fireEvent(ureq, FOLDERCOMMAND_FINISHED);
				}
				for (String file : files) {
					VFSItem item = currentContainer.resolve(file);
					if (item != null && (item.canDelete() == VFSConstants.YES)) {
						if (versionsEnabled && item.canVersion() == VFSConstants.YES) {
							// Move to pub
							item.delete();
						} else {
							item.deleteSilently();
						}
					} else {
						getWindowControl().setWarning(translator.translate("del.partial"));
					}
				}

				String confirmationText = fileSelection.renderAsHtml();
				fireEvent(ureq, new FolderEvent(FolderEvent.DELETE_EVENT, confirmationText));
				fireEvent(ureq, FOLDERCOMMAND_FINISHED);
			} else {
				// abort
				status = FolderCommandStatus.STATUS_CANCELED;
				fireEvent(ureq, FOLDERCOMMAND_FINISHED);
			}
		}

	}

	@Override
	protected void event(UserRequest ureq, Component source, Event event) {
		// no events to catch
	}

	@Override
	protected void doDispose() {
		// autodisposed by basic controller
	}
}

## 内容3
### 文件路径：
src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java
### 代码内容：
/**
 *
 * OpenOLAT - Online Learning and Training

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at the
 * Apache homepage
 *

 * Unless required by applicable law or agreed to in writing,

 * software distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.
 *

 * Initial code contributed and copyrighted by

 * frentix GmbH, http://www.frentix.com
 *

 */
package org.olat.core.commons.modules.bc.commands;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.Logger;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.FileSelection;
import org.olat.core.commons.modules.bc.components.FolderComponent;
import org.olat.core.commons.services.vfs.VFSRepositoryService;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.ServletUtil;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.filters.VFSSystemItemFilter;

/**
 *
 * Initial date: 31.10.2014

 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
 *
 */
public class CmdDownloadZip implements FolderCommand {

	private static final Logger log = Tracing.createLoggerFor(CmdDownloadZip.class);

	private int status = FolderCommandStatus.STATUS_SUCCESS;

	@Override
	public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator trans) {
		VFSContainer currentContainer = folderComponent.getCurrentContainer();

		status = FolderCommandHelper.sanityCheck(wControl, folderComponent);
		if(status == FolderCommandStatus.STATUS_FAILED) {
			return null;
		}

		FileSelection selection = new FileSelection(ureq, folderComponent.getCurrentContainerPath());
		status = FolderCommandHelper.sanityCheck3(wControl, folderComponent, selection);
		if(status == FolderCommandStatus.STATUS_FAILED) {
			return null;
		}

		if(selection.getFiles().isEmpty()) {
			status = FolderCommandStatus.STATUS_FAILED;
			wControl.setWarning(trans.translate("warning.file.selection.empty"));
			return null;
		}

		MediaResource mr = new ZipMediaResource(currentContainer, selection);
		ureq.getDispatchResult().setResultingMediaResource(mr);
		return null;
	}

	@Override
	public int getStatus() {
		return status;
	}

	@Override
	public boolean runsModal() {
		return false;
	}

	@Override
	public String getModalTitle() {
		return null;
	}

	private static class ZipMediaResource implements MediaResource {

		private final FileSelection selection;
		private final VFSContainer currentContainer;

		public ZipMediaResource(VFSContainer currentContainer, FileSelection selection) {
			this.selection = selection;
			this.currentContainer = currentContainer;
		}

		@Override
		public long getCacheControlDuration() {
			return ServletUtil.CACHE_NO_CACHE;
		}

		@Override
		public boolean acceptRanges() {
			return false;
		}

		@Override
		public String getContentType() {
			return "application/zip";
		}

		@Override
		public Long getSize() {
			return null;
		}

		@Override
		public InputStream getInputStream() {
			return null;
		}

		@Override
		public Long getLastModified() {
			return null;
		}

		@Override
		public void prepare(HttpServletResponse hres) {
			List<String> selectedFiles = selection.getFiles();

			String urlEncodedLabel;
			if(selectedFiles.size() == 1) {
				String filename = selectedFiles.get(0);
				int lastIndexOf = filename.lastIndexOf('.');
				if(lastIndexOf > 0) {
					filename = filename.substring(0, lastIndexOf);
				}
				urlEncodedLabel = StringHelper.urlEncodeUTF8(filename + ".zip");
			} else  {
				urlEncodedLabel = "Archive.zip";
			}

			hres.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + urlEncodedLabel);
			hres.setHeader("Content-Description", urlEncodedLabel);

			if(selectedFiles.size() == 1 && selectedFiles.get(0).toLowerCase().endsWith(".zip")) {
				VFSItem singleItem = currentContainer.resolve(selectedFiles.get(0));
				if(singleItem instanceof VFSLeaf) {
					try(OutputStream out = hres.getOutputStream()) {
						VFSManager.copyContent((VFSLeaf)singleItem, out);
					} catch(IOException e) {
						log.error("", e);
					}
				} else {
					prepareZip(hres, selectedFiles);
				}
			} else {
				prepareZip(hres, selectedFiles);
			}
		}

		private void prepareZip(HttpServletResponse hres, List<String> selectedFiles) {
			VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
			try(ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) {
				zout.setLevel(9);

				List<VFSItem> vfsFiles = new ArrayList<>();
				for (String fileName : selectedFiles) {
					VFSItem item = currentContainer.resolve(fileName);
					if (item != null) {
						vfsFiles.add(item);
						// update download counter
						if (item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
							vfsRepositoryService.increaseDownloadCount((VFSLeaf)item);
						}
					}
				}

				for (VFSItem item:vfsFiles) {
					ZipUtil.addToZip(item, "", zout, new VFSSystemItemFilter(), false);
				}
				zout.flush();
			} catch (IOException e) {
				String className = e.getClass().getSimpleName();
				if("ClientAbortException".equals(className)) {
					log.debug("client browser probably abort when downloading zipped files", e);
				} else {
					log.error("client browser probably abort when downloading zipped files", e);
				}
			} catch (Exception e) {
				log.error("", e);
			}
		}

		@Override
		public void release() {
			//
		}
	}
}
## 内容4
### 文件路径：
src/main/java/org/olat/core/commons/modules/bc/commands/CmdMoveCopy.java
### 代码内容：
package org.olat.core.commons.modules.bc.commands;

import java.util.ArrayList;
import java.util.List;

import org.olat.core.commons.modules.bc.FileSelection;
import org.olat.core.commons.modules.bc.FolderEvent;
import org.olat.core.commons.modules.bc.components.FolderComponent;
import org.olat.core.commons.services.notifications.NotificationsManager;
import org.olat.core.commons.services.notifications.SubscriptionContext;
import org.olat.core.commons.services.vfs.VFSRepositoryService;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.tree.MenuTree;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.DefaultController;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.folder.FolderTreeModel;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.Util;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSLockApplicationType;
import org.olat.core.util.vfs.VFSLockManager;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.VFSStatus;
import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
import org.olat.core.util.vfs.filters.VFSItemFilter;
import org.springframework.beans.factory.annotation.Autowired;

public class CmdMoveCopy extends DefaultController implements FolderCommand {

	private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CmdMoveCopy.class);
	private static int status = FolderCommandStatus.STATUS_SUCCESS;

	private Translator translator;

	private MenuTree selTree;
	private FileSelection fileSelection;
	private Link selectButton, cancelButton;
	private FolderComponent folderComponent;
	private final boolean move;

	@Autowired
	private VFSLockManager vfsLockManager;
	@Autowired
	private VFSRepositoryService vfsRepositoryService;
	@Autowired
	private NotificationsManager notificationsManager;

	protected CmdMoveCopy(WindowControl wControl, boolean move) {
		super(wControl);
		this.move = move;
	}

	@Override
	public Controller execute(FolderComponent fc, UserRequest ureq, WindowControl windowControl, Translator trans) {
		this.folderComponent = fc;
		this.translator = trans;
		this.fileSelection = new FileSelection(ureq, fc.getCurrentContainerPath());

		VelocityContainer main = new VelocityContainer("mc", VELOCITY_ROOT + "/movecopy.html", translator, this);
		main.contextPut("fileselection", fileSelection);

		//check if command is executed on a file list containing invalid filenames or paths
		if(!fileSelection.getInvalidFileNames().isEmpty()) {
			main.contextPut("invalidFileNames", fileSelection.getInvalidFileNames());
		}

		selTree = new MenuTree(null, "seltree", this);
		FolderTreeModel ftm = new FolderTreeModel(ureq.getLocale(), fc.getRootContainer(),
				true, false, true, fc.getRootContainer().canWrite() == VFSConstants.YES, new EditableFilter());
		selTree.setTreeModel(ftm);
		selectButton = LinkFactory.createButton(move ? "move" : "copy", main, this);
		cancelButton = LinkFactory.createButton("cancel", main, this);

		main.put("seltree", selTree);
		if (move) {
			main.contextPut("move", Boolean.TRUE);
		}

		setInitialComponent(main);
		return this;
	}

	public boolean isMoved() {
		return move;
	}

	public FileSelection getFileSelection() {
		return fileSelection;
	}

	@Override
	public int getStatus() {
		return status;
	}

	@Override
	public boolean runsModal() {
		return false;
	}

	@Override
	public String getModalTitle() {
		return null;
	}

	public String getTarget() {
		FolderTreeModel ftm = (FolderTreeModel) selTree.getTreeModel();
		return ftm.getSelectedPath(selTree.getSelectedNode());
	}

	@Override
	public void event(UserRequest ureq, Component source, Event event) {
		if(cancelButton == source) {
			status = FolderCommandStatus.STATUS_CANCELED;
			fireEvent(ureq, FOLDERCOMMAND_FINISHED);
		} else if (selectButton == source) {
			doMove(ureq);
		}
	}

	private void doMove(UserRequest ureq) {
		FolderTreeModel ftm = (FolderTreeModel) selTree.getTreeModel();
		String selectedPath = ftm.getSelectedPath(selTree.getSelectedNode());
		if (selectedPath == null) {
			abortFailed(ureq, "failed");
			return;
		}
		VFSStatus vfsStatus = VFSConstants.SUCCESS;
		VFSContainer rootContainer = folderComponent.getRootContainer();
		VFSItem vfsItem = rootContainer.resolve(selectedPath);
		if (vfsItem == null || (vfsItem.canWrite() != VFSConstants.YES)) {
			abortFailed(ureq, "failed");
			return;
		}
		// copy the files
		VFSContainer target = (VFSContainer)vfsItem;
		List<VFSItem> sources = getSanityCheckedSourceItems(target, ureq);
		if (sources == null) return;

		for (VFSItem vfsSource:sources) {
			VFSItem targetFile = target.resolve(vfsSource.getName());
			if(vfsSource instanceof VFSLeaf && targetFile != null && targetFile.canVersion() == VFSConstants.YES) {
				//add a new version to the file
				VFSLeaf sourceLeaf = (VFSLeaf)vfsSource;
				vfsRepositoryService.addVersion(sourceLeaf, ureq.getIdentity(), false, "", sourceLeaf.getInputStream());
			} else {
				vfsStatus = target.copyFrom(vfsSource, ureq.getIdentity());
			}
			if (vfsStatus != VFSConstants.SUCCESS) {
				String errorKey = "failed";
				if (vfsStatus == VFSConstants.ERROR_QUOTA_EXCEEDED)
					errorKey = "QuotaExceeded";
				abortFailed(ureq, errorKey);
				return;
			}
			if (move) {
				// if move, delete the source. Note that meta source
				// has already been delete (i.e. moved)
				vfsSource.delete();
			}
		}

		// after a copy or a move, notify the subscribers
		VFSSecurityCallback secCallback = VFSManager.findInheritedSecurityCallback(folderComponent.getCurrentContainer());
		if (secCallback != null) {
			SubscriptionContext subsContext = secCallback.getSubscriptionContext();
			if (subsContext != null) {
				notificationsManager.markPublisherNews(subsContext, ureq.getIdentity(), true);
			}
		}
		fireEvent(ureq, new FolderEvent(move ? FolderEvent.MOVE_EVENT : FolderEvent.COPY_EVENT, fileSelection.renderAsHtml()));
		notifyFinished(ureq);
	}

	private void notifyFinished(UserRequest ureq) {
		VFSContainer container = VFSManager.findInheritingSecurityCallbackContainer(folderComponent.getRootContainer());
		VFSSecurityCallback secCallback = container.getLocalSecurityCallback();
		if(secCallback != null) {
			SubscriptionContext subsContext = secCallback.getSubscriptionContext();
			if (subsContext != null) {
				notificationsManager.markPublisherNews(subsContext, ureq.getIdentity(), true);
			}
		}
		fireEvent(ureq, FOLDERCOMMAND_FINISHED);
	}

	/**
	 * Get the list of source files. Sanity check if resolveable, overlapping or
	 * a target with the same name already exists. In such cases, set the error message, fire
	 * the abort event and return null.
	 *
	 * @param target
	 * @param ureq
	 * @return
	 */
	private List<VFSItem> getSanityCheckedSourceItems(VFSContainer target, UserRequest ureq) {
		// collect all source files first

		List<VFSItem> sources = new ArrayList<>();
		for (String sourceRelPath:fileSelection.getFiles()) {
			VFSItem vfsSource = folderComponent.getCurrentContainer().resolve(sourceRelPath);
			if (vfsSource == null) {
				abortFailed(ureq, "FileDoesNotExist");
				return null;
			}
			if (vfsSource instanceof VFSContainer) {
				// if a folder... check if they are overlapping
				if (VFSManager.isContainerDescendantOrSelf(target, (VFSContainer)vfsSource)) {
					abortFailed(ureq, "OverlappingTarget");
					return null;
				}
			}
			if (vfsLockManager.isLockedForMe(vfsSource, ureq.getIdentity(), VFSLockApplicationType.vfs, null)) {
				abortFailed(ureq, "lock.title");
				return null;
			}

			// check for existence... this will also prevent to copy item over itself
			VFSItem item = target.resolve(vfsSource.getName());
			if (item != null) {
				abortFailed(ureq, "TargetNameAlreadyUsed");
				return null;
			}

			if (vfsSource.canCopy() != VFSConstants.YES) {
				getWindowControl().setError(translator.translate("FileMoveCopyFailed", new String[] {vfsSource.getName()}));
				status = FolderCommandStatus.STATUS_FAILED;
				fireEvent(ureq, FOLDERCOMMAND_FINISHED);
				return null;
			}
			sources.add(vfsSource);
		}
		return sources;
	}

	private void abortFailed(UserRequest ureq, String errorMessageKey) {
		getWindowControl().setError(translator.translate(errorMessageKey));
		status = FolderCommandStatus.STATUS_FAILED;
		fireEvent(ureq, FOLDERCOMMAND_FINISHED);
	}

	@Override
	protected void doDispose() {
		//
	}

	private static final class EditableFilter implements VFSItemFilter {

		@Override
		public boolean accept(VFSItem vfsItem) {
			VFSSecurityCallback secCallback = vfsItem.getLocalSecurityCallback();
			if(secCallback != null && !secCallback.canWrite()) {
				return false;
			}
			return true;
		}
	}
}
## 内容5
### 文件路径：
src/main/java/org/olat/core/commons/modules/bc/commands/CmdUnzip.java
### 代码内容：
package org.olat.core.commons.modules.bc.commands;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.olat.core.commons.modules.bc.FileSelection;
import org.olat.core.commons.modules.bc.components.FolderComponent;
import org.olat.core.commons.services.notifications.NotificationsManager;
import org.olat.core.commons.services.notifications.SubscriptionContext;
import org.olat.core.commons.services.vfs.VFSMetadata;
import org.olat.core.commons.services.vfs.VFSRepositoryService;
import org.olat.core.commons.services.vfs.model.VFSMetadataImpl;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.logging.AssertException;
import org.olat.core.util.StringHelper;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.vfs.Quota;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
import org.springframework.beans.factory.annotation.Autowired;

public class CmdUnzip extends BasicController implements FolderCommand {

	private int status = FolderCommandStatus.STATUS_SUCCESS;

	private Translator translator;
	private DialogBoxController lockedFiledCtr;

	@Autowired
	private VFSRepositoryService vfsRepositoryService;
	@Autowired
	private NotificationsManager notificationsManager;

	public CmdUnzip(UserRequest ureq, WindowControl wControl) {
		super(ureq, wControl);
	}

	@Override
	public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wContr, Translator trans) {
		this.translator = trans;
		FileSelection selection = new FileSelection(ureq, folderComponent.getCurrentContainerPath());
		VFSContainer currentContainer = folderComponent.getCurrentContainer();
		if (currentContainer.canWrite() != VFSConstants.YES)
			throw new AssertException("Cannot unzip to folder. Writing denied.");

	  //check if command is executed on a file containing invalid filenames or paths - checks if the resulting folder has a valid name
		if(selection.getInvalidFileNames().size()>0) {
			status = FolderCommandStatus.STATUS_INVALID_NAME;
			return null;
		}

		List<String> lockedFiles = new ArrayList<>();
		for (String sItem:selection.getFiles()) {
			VFSItem vfsItem = currentContainer.resolve(sItem);
			if (vfsItem instanceof VFSLeaf) {
				try {
					lockedFiles.addAll(checkLockedFiles((VFSLeaf)vfsItem, currentContainer, ureq.getIdentity()));
				} catch (Exception e) {
					String name = vfsItem == null ? "NULL" : vfsItem.getName();
					getWindowControl().setError(translator.translate("FileUnzipFailed", new String[]{name}));
				}
			}
		}

		if(!lockedFiles.isEmpty()) {
			String msg = FolderCommandHelper.renderLockedMessageAsHtml(trans, lockedFiles);
			List<String> buttonLabels = Collections.singletonList(trans.translate("ok"));
			lockedFiledCtr = activateGenericDialog(ureq, trans.translate("lock.title"), msg, buttonLabels, lockedFiledCtr);
			return null;
		}

		VFSItem currentVfsItem = null;
		try {
			boolean fileNotExist = false;
			for (String sItem:selection.getFiles()) {
				currentVfsItem = currentContainer.resolve(sItem);
				if (currentVfsItem instanceof VFSLeaf) {
					if (!doUnzip((VFSLeaf)currentVfsItem, currentContainer, ureq, wContr)) {
						status = FolderCommandStatus.STATUS_FAILED;
						break;
					}
				} else {
					fileNotExist = true;
					break;
				}
			}

			if (fileNotExist) {
				status = FolderCommandStatus.STATUS_FAILED;
				getWindowControl().setError(translator.translate("FileDoesNotExist"));
			}

			VFSContainer inheritingCont = VFSManager.findInheritingSecurityCallbackContainer(folderComponent.getRootContainer());
			if(inheritingCont != null) {
				VFSSecurityCallback secCallback = inheritingCont.getLocalSecurityCallback();
				if(secCallback != null) {
					SubscriptionContext subsContext = secCallback.getSubscriptionContext();
					if (subsContext != null) {
						notificationsManager.markPublisherNews(subsContext, ureq.getIdentity(), true);
					}
				}
			}
		} catch (IllegalArgumentException e) {
			logError("Corrupted ZIP", e);
			String name = currentVfsItem == null ? "NULL" : currentVfsItem.getName();
			getWindowControl().setError(translator.translate("FileUnzipFailed", new String[]{name}));
		}

		return null;
	}

	private List<String> checkLockedFiles(VFSLeaf vfsItem, VFSContainer currentContainer, Identity identity) {
		String name = vfsItem.getName();
		if (!name.toLowerCase().endsWith(".zip")) {
			return Collections.emptyList();
		}

		if(currentContainer.canVersion() != VFSConstants.YES) {
			//this command don't overwrite existing folders
			return Collections.emptyList();
		}

		String sZipContainer = name.substring(0, name.length() - 4);
		VFSItem zipContainer = currentContainer.resolve(sZipContainer);
		if(zipContainer == null) {
			return Collections.emptyList();
		} else if (zipContainer instanceof VFSContainer) {
			return ZipUtil.checkLockedFileBeforeUnzipNonStrict(vfsItem, (VFSContainer)zipContainer, identity);
		} else {
			//replace a file with a folder ???
			return Collections.emptyList();
		}
	}

	private boolean doUnzip(VFSLeaf vfsItem, VFSContainer currentContainer, UserRequest ureq, WindowControl wControl) {
		String name = vfsItem.getName();
		if (!name.toLowerCase().endsWith(".zip")) {
			wControl.setError(translator.translate("FileUnzipFailed", new String[] {vfsItem.getName()}));
			return false;
		}

		// we make a new folder with the same name as the zip file
		String sZipContainer = name.substring(0, name.length() - 4);

		boolean versioning = currentContainer.canVersion() == VFSConstants.YES;

		VFSContainer zipContainer = currentContainer.createChildContainer(sZipContainer);
		if (zipContainer == null) {
			if(versioning) {
				VFSItem resolvedItem = currentContainer.resolve(sZipContainer);
				if(resolvedItem instanceof VFSContainer) {
					zipContainer = (VFSContainer)resolvedItem;
				} else {
					String numberedFilename = findContainerName(currentContainer, sZipContainer);
					if(StringHelper.containsNonWhitespace(numberedFilename)) {
						zipContainer = currentContainer.createChildContainer(numberedFilename);
					}
					if(zipContainer == null) {// we try our best
						wControl.setError(translator.translate("unzip.alreadyexists", new String[] {sZipContainer}));
						return false;
					}
				}
			} else {
				// folder already exists... issue warning
				wControl.setError(translator.translate("unzip.alreadyexists", new String[] {sZipContainer}));
				return false;
			}
		} else if (zipContainer.canMeta() == VFSConstants.YES) {
			VFSMetadata info = zipContainer.getMetaInfo();
			if(info instanceof VFSMetadataImpl) {
				((VFSMetadataImpl)info).setFileInitializedBy(ureq.getIdentity());
				vfsRepositoryService.updateMetadata(info);
			}
		}

		if (!ZipUtil.unzipNonStrict(vfsItem, zipContainer, ureq.getIdentity(), versioning)) {
			// operation failed - rollback
			zipContainer.delete();
			wControl.setError(translator.translate("failed"));
			return false;
		} else {
			// check quota
			long quotaLeftKB = VFSManager.getQuotaLeftKB(currentContainer);
			if (quotaLeftKB != Quota.UNLIMITED && quotaLeftKB < 0) {
				// quota exceeded - rollback
				zipContainer.delete();
				wControl.setError(translator.translate("QuotaExceeded"));
				return false;
			}
		}
		return true;
	}

	private String findContainerName(VFSContainer container, String filename) {
		String newName = filename;
		VFSItem newFile = container.resolve(newName);
		for(int count=1; newFile != null && count < 999 ; count++) {
			newName = filename + "_" + count;
		    newFile = container.resolve(newName);
		}
		if(newFile == null) {
			return newName;
		}
		return null;
	}

	@Override
	public int getStatus() {
		return status;
	}

	@Override
	public boolean runsModal() {
		return false;
	}

	@Override
	public String getModalTitle() {
		return null;
	}

	@Override
	protected void doDispose() {
		//autodisposed by BasicController
	}

	@Override
	protected void event(UserRequest ureq, Component source, Event event) {
		// no events to catch
	}

	@Override
	public void event(UserRequest ureq, Controller source, Event event) {
		// no events to catch
	}

}

## 内容6
### 文件路径：
src/main/java/org/olat/core/commons/modules/bc/commands/CmdZip.java
### 代码内容：
package org.olat.core.commons.modules.bc.commands;

import java.util.ArrayList;
import java.util.List;

import org.olat.core.commons.modules.bc.FileSelection;
import org.olat.core.commons.modules.bc.FolderEvent;
import org.olat.core.commons.modules.bc.components.FolderComponent;
import org.olat.core.commons.services.vfs.VFSRepositoryService;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.AssertException;
import org.olat.core.util.FileUtils;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.filters.VFSSystemItemFilter;
import org.springframework.beans.factory.annotation.Autowired;

/**
 *
 * Description:

 * Provides a CreateItemForm and creates a zip file if input valid.
 *
 *

 * Initial Date:  30.01.2008

 * @author Lavinia Dumitrescu
 */
public class CmdZip extends FormBasicController implements FolderCommand {

	private int status = FolderCommandStatus.STATUS_SUCCESS;

	private VFSContainer currentContainer;
	private FileSelection selection;
	private TextElement textElement;

	@Autowired
	private VFSRepositoryService vfsRepositoryService;

	protected CmdZip(UserRequest ureq, WindowControl wControl) {
		super(ureq, wControl);
	}

	@Override
	public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator trans) {
		setTranslator(trans);
		currentContainer = folderComponent.getCurrentContainer();
		if (currentContainer.canWrite() != VFSConstants.YES) {
			throw new AssertException("Cannot write to current folder.");
		}

		status = FolderCommandHelper.sanityCheck(wControl, folderComponent);
		if(status == FolderCommandStatus.STATUS_FAILED) {
			return null;
		}

		selection = new FileSelection(ureq, folderComponent.getCurrentContainerPath());
		status = FolderCommandHelper.sanityCheck3(wControl, folderComponent, selection);
		if(status == FolderCommandStatus.STATUS_FAILED) {
			return null;
		}

		if(selection.getFiles().isEmpty()) {
			status = FolderCommandStatus.STATUS_FAILED;
			wControl.setWarning(trans.translate("warning.file.selection.empty"));
			return null;
		}

		initForm(ureq);
		return this;
	}

	@Override
	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
		String files = selection.renderAsHtml();
		uifactory.addStaticExampleText("zip.confirm", files, formLayout);

		textElement = uifactory.addTextElement("fileName", "zip.name", 20, "", formLayout);
		textElement.setMandatory(true);
		uifactory.addStaticTextElement("extension", null, translate("zip.extension"), formLayout);

		FormLayoutContainer formButtons = FormLayoutContainer.createButtonLayout("formButton", getTranslator());
		formLayout.add(formButtons);
		uifactory.addFormSubmitButton("submit","zip.button", formButtons);
		uifactory.addFormCancelButton("cancel", formButtons, ureq, getWindowControl());
	}

	@Override
	protected void doDispose() {
		// nothing to do
	}

	@Override
	public int getStatus() {
		return status;
	}

	@Override
	public boolean runsModal() {
		return false;
	}

	@Override
	public String getModalTitle() {
		return translate("zip.header");
	}

	@Override
	protected void formCancelled(UserRequest ureq) {
		status = FolderCommandStatus.STATUS_CANCELED;
		fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED);
	}

	/**
	 * Creates a zipFile by using ZipUtil and fires Event.DONE_EVENT if successful.
	 */
	@Override
	protected void formOK(UserRequest ureq) {
		String name = textElement.getValue();
		if(!name.toLowerCase().endsWith(".zip")) {
			name += ".zip";
		}

		VFSLeaf zipFile = currentContainer.createChildLeaf(name);
		if (zipFile == null) {
			fireEvent(ureq, Event.FAILED_EVENT);
			return;
		}

		List<VFSItem> vfsFiles = new ArrayList<>();
		for (String fileName : selection.getFiles()) {
			VFSItem item = currentContainer.resolve(fileName);
			if (item != null) {
				vfsFiles.add(item);
			}
		}
		if (!ZipUtil.zip(vfsFiles, zipFile, new VFSSystemItemFilter(), false)) {
			zipFile.delete();
			status = FolderCommandStatus.STATUS_FAILED;
			fireEvent(ureq, FOLDERCOMMAND_FINISHED);
		} else {
			vfsRepositoryService.itemSaved(zipFile, ureq.getIdentity());

			fireEvent(ureq, new FolderEvent(FolderEvent.ZIP_EVENT, selection.renderAsHtml()));
			fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED);
		}
	}

	/**
	 * Checks if input valid.
	 * @see org.olat.core.commons.modules.bc.commands.AbstractCreateItemForm#validateFormLogic(org.olat.core.gui.UserRequest)
	 */
	@Override
	protected boolean validateFormLogic(UserRequest ureq) {
		boolean isInputValid = true;
		String name = textElement.getValue();
		if(name==null || name.trim().equals("")) {
			textElement.setErrorKey("zip.name.empty", new String[0]);
			isInputValid = false;
		} else {
			if (!validateFileName(name)) {
				textElement.setErrorKey("zip.name.notvalid", new String[0]);
				isInputValid = false;
				return isInputValid;
			}
    //Note: use java.io.File and not VFS to create a leaf. File must not exist upon ZipUtil.zip()
			name = name + ".zip";
			VFSItem zipFile = currentContainer.resolve(name);
			if (zipFile != null) {
				textElement.setErrorKey("zip.alreadyexists", new String[] {name});
				isInputValid = false;
			} else {
				isInputValid = true;
			}
		}
		return isInputValid;
	}

	/**
	 * Checks if filename contains any prohibited chars.
	 * @param name
	 * @return true if file name valid.
	 */
	private boolean validateFileName(String name) {
		return FileUtils.validateFilename(name);
	}
}
## 内容7
### 文件路径：
src/main/java/org/olat/core/util/mail/ui/SendDocumentsByEMailController.java
### 代码内容：
/**
 *
 * OpenOLAT - Online Learning and Training

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at the
 * Apache homepage
 *

 * Unless required by applicable law or agreed to in writing,

 * software distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.
 *

 * Initial code contributed and copyrighted by

 * frentix GmbH, http://www.frentix.com
 *

 */
package org.olat.core.util.mail.ui;

import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.events.SingleIdentityChosenEvent;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.FileSelection;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.commons.modules.bc.commands.CmdSendMail;
import org.olat.core.commons.modules.bc.commands.FolderCommand;
import org.olat.core.commons.modules.bc.commands.FolderCommandHelper;
import org.olat.core.commons.modules.bc.commands.FolderCommandStatus;
import org.olat.core.commons.modules.bc.components.FolderComponent;
import org.olat.core.commons.modules.bc.meta.MetaInfoController;
import org.olat.core.commons.services.vfs.VFSMetadata;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
import org.olat.core.gui.control.generic.folder.FolderHelper;
import org.olat.core.gui.translator.Translator;
import org.olat.core.gui.util.CSSHelper;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.id.UserConstants;
import org.olat.core.id.context.BusinessControlFactory;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.mail.MailBundle;
import org.olat.core.util.mail.MailHelper;
import org.olat.core.util.mail.MailManager;
import org.olat.core.util.mail.MailModule;
import org.olat.core.util.mail.MailerResult;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.olat.user.UserManager;
import org.springframework.beans.factory.annotation.Autowired;

/**
 *
 *
Description:

 *

 *

 * Initial Date: 7 feb. 2011

 *
 * @author srosse, stephane.rosse@frentix.com, www.frentix.com
 */
public class SendDocumentsByEMailController extends FormBasicController implements CmdSendMail {

	private TextElement bodyElement;
	private FormLink addEmailLink;
	private TextElement subjectElement;
	private FormLayoutContainer userListBox;
	private FormLayoutContainer attachmentsLayout;
	private EMailCalloutCtrl emailCalloutCtrl;
	private CloseableCalloutWindowController calloutCtrl;

	private final DecimalFormat formatMb = new DecimalFormat("0.00");

	private int status = FolderCommandStatus.STATUS_SUCCESS;
	private List<File> attachments;
	private final boolean allowAttachments;
	private List<IdentityWrapper> toValues = new ArrayList<>();

	@Autowired
	private UserManager userManager;
	@Autowired
	private MailManager mailManager;
	@Autowired
	private BaseSecurity securityManager;

	public SendDocumentsByEMailController(UserRequest ureq, WindowControl wControl) {
		super(ureq, wControl, null, Util.createPackageTranslator(MailModule.class, ureq.getLocale(),
				Util.createPackageTranslator(MetaInfoController.class, ureq.getLocale())));
		setBasePackage(MailModule.class);

		allowAttachments = !FolderConfig.getSendDocumentLinkOnly();

		initForm(ureq);
	}

	@Override
	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
		setFormDescription("send.mail.description");
		setFormStyle("o_send_documents");

		int emailCols = 25;

		String toPage = velocity_root + "/tos.html";
		userListBox = FormLayoutContainer.createCustomFormLayout("send.mail.to.auto", getTranslator(), toPage);
		userListBox.setLabel("send.mail.to", null);
		userListBox.setRootForm(mainForm);
		userListBox.contextPut("tos", toValues);
		formLayout.add(userListBox);

		addEmailLink = uifactory.addFormLink("add.email", userListBox);
		addEmailLink.setIconLeftCSS("o_icon o_icon_add");

		subjectElement = uifactory.addTextElement("tsubject", "send.mail.subject", 255, "", formLayout);

		bodyElement = uifactory.addTextAreaElement("tbody", "send.mail.body", -1, 20, emailCols, false, false, "", formLayout);

		if (allowAttachments) {
			String page = Util.getPackageVelocityRoot(MailModule.class) + "/sendattachments.html";
			attachmentsLayout = FormLayoutContainer.createCustomFormLayout("attachments", getTranslator(), page);
			attachmentsLayout.setRootForm(mainForm);
			attachmentsLayout.setLabel("send.mail.attachments", null);
			formLayout.add(attachmentsLayout);
		}

		FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
		formLayout.add(buttonGroupLayout);
		uifactory.addFormSubmitButton("ok", buttonGroupLayout);
		uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
	}

	@Override
	protected void doDispose() {
		//
	}

	@Override
	public int getStatus() {
		return status;
	}

	@Override
	public boolean runsModal() {
		return false;
	}

	@Override
	public String getModalTitle() {
		return translate("send.mail.title");
	}

	public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator translator) {
		VFSContainer currentContainer = folderComponent.getCurrentContainer();
		VFSContainer rootContainer = folderComponent.getRootContainer();

		if (!VFSManager.exists(currentContainer)) {
			status = FolderCommandStatus.STATUS_FAILED;
			showError(translator.translate("FileDoesNotExist"));
			return null;
		}
		status = FolderCommandHelper.sanityCheck(wControl, folderComponent);
		if (status == FolderCommandStatus.STATUS_FAILED) {
			return null;
		}
		FileSelection selection = new FileSelection(ureq, folderComponent.getCurrentContainerPath());
		status = FolderCommandHelper.sanityCheck3(wControl, folderComponent, selection);
		if (status == FolderCommandStatus.STATUS_FAILED) {
			return null;
		}

		boolean selectionWithContainer = false;
		List<String> filenames = selection.getFiles();
		List<VFSLeaf> leafs = new ArrayList<>();
		for (String file : filenames) {
			VFSItem item = currentContainer.resolve(file);
			if (item instanceof VFSContainer) {
				selectionWithContainer = true;
			} else if (item instanceof VFSLeaf) {
				leafs.add((VFSLeaf) item);
			}
		}
		if (selectionWithContainer) {
			if (leafs.isEmpty()) {
				wControl.setError(getTranslator().translate("send.mail.noFileSelected"));
				return null;
			} else {
				setFormWarning(getTranslator().translate("send.mail.selectionContainsFolder"));
			}
		}
		setFiles(rootContainer, leafs);
		return this;
	}

	protected void setFiles(VFSContainer rootContainer, List<VFSLeaf> files) {
		StringBuilder subjectSb = new StringBuilder();
		if (StringHelper.containsNonWhitespace(subjectElement.getValue())) {
			subjectSb.append(subjectElement.getValue()).append('\n').append('\n');
		}
		StringBuilder bodySb = new StringBuilder();
		if (StringHelper.containsNonWhitespace(bodyElement.getValue())) {
			bodySb.append(bodyElement.getValue()).append('\n').append('\n');
		}

		attachments = new ArrayList<>();
		long fileSize = 0l;
		for (VFSLeaf file : files) {
			VFSMetadata infos = null;
			if (file.canMeta() == VFSConstants.YES) {
				infos = file.getMetaInfo();
			}
			// subject
			appendToSubject(file, infos, subjectSb);

			// body
			appendMetadatas(file, infos, bodySb);
			appendBusinessPath(rootContainer, file, bodySb);
			bodySb.append('\n').append('\n');
			fileSize += file.getSize();
			if (allowAttachments && file instanceof LocalFileImpl) {
				File f = ((LocalFileImpl) file).getBasefile();
				attachments.add(f);
			}
		}

		int mailQuota = CoreSpringFactory.getImpl(MailModule.class).getMaxSizeForAttachement();
		long fileSizeInMB = fileSize / (1024l * 1024l);
		if (allowAttachments) {
			if (fileSizeInMB > mailQuota) {
				attachments.clear();
				setFormWarning("send.mail.fileToBigForAttachments", new String[] { String.valueOf(mailQuota), String.valueOf(fileSizeInMB) });
			} else {
				List<FileInfo> infos = new ArrayList<>(files.size());
				for (VFSLeaf file : files) {
					final String name = file.getName();
					final double size = file.getSize() / (1024.0 * 1024.0);
					final String sizeStr = formatMb.format(size);
					final String cssClass = CSSHelper.createFiletypeIconCssClassFor(file.getName());
					infos.add(new FileInfo(name, sizeStr, cssClass));
				}
				attachmentsLayout.contextPut("attachments", infos);
			}
		}

		subjectElement.setValue(subjectSb.toString());
		bodyElement.setValue(bodySb.toString());
	}

	protected void appendToSubject(VFSLeaf file, VFSMetadata infos, StringBuilder sb) {
		if (sb.length() > 0)
			sb.append(", ");
		if (infos != null && StringHelper.containsNonWhitespace(infos.getTitle())) {
			sb.append(infos.getTitle());
		} else {
			sb.append(file.getName());
		}
	}

	protected void appendMetadatas(VFSLeaf file, VFSMetadata infos, StringBuilder sb) {
		if (infos == null) {
			appendMetadata("mf.filename", file.getName(), sb);
		} else {
			appendMetadata("mf.filename", infos.getFilename(), sb);
			String title = infos.getTitle();
			if (StringHelper.containsNonWhitespace(title)) {
				appendMetadata("mf.title", title, sb);
			}
			String comment = infos.getComment();
			if (StringHelper.containsNonWhitespace(comment)) {
				appendMetadata("mf.comment", comment, sb);
			}
			String creator = infos.getCreator();
			if (StringHelper.containsNonWhitespace(creator)) {
				appendMetadata("mf.creator", creator, sb);
			}
			String publisher = infos.getPublisher();
			if (StringHelper.containsNonWhitespace(publisher)) {
				appendMetadata("mf.publisher", publisher, sb);
			}
			String source = infos.getSource();
			if (StringHelper.containsNonWhitespace(source)) {
				appendMetadata("mf.source", source, sb);
			}
			String city = infos.getCity();
			if (StringHelper.containsNonWhitespace(city)) {
				appendMetadata("mf.city", city, sb);
			}
			appendPublicationDate(infos, sb);
			String pages = infos.getPages();
			if (StringHelper.containsNonWhitespace(pages)) {
				appendMetadata("mf.pages", pages, sb);
			}
			String language = infos.getLanguage();
			if (StringHelper.containsNonWhitespace(language)) {
				appendMetadata("mf.language", language, sb);
			}
			String url = infos.getUrl();
			if (StringHelper.containsNonWhitespace(url)) {
				appendMetadata("mf.url", url, sb);
			}
			String author = userManager.getUserDisplayName(infos.getFileInitializedBy());
			if (StringHelper.containsNonWhitespace(author)) {
				appendMetadata("mf.author", author, sb);
			}
			String size = Formatter.formatBytes(file.getSize());
			appendMetadata("mf.size", size, sb);
			Date lastModifiedDate = infos.getFileLastModified();
			if (lastModifiedDate != null) {
				appendMetadata("mf.lastModified", Formatter.getInstance(getLocale()).formatDate(lastModifiedDate), sb);
			}
			String type = FolderHelper.extractFileType(file.getName(), getLocale());
			if (StringHelper.containsNonWhitespace(type)) {
				appendMetadata("mf.type", type, sb);
			}
			int downloads = infos.getDownloadCount();
			if (infos.getDownloadCount() >= 0) {
				appendMetadata("mf.downloads", String.valueOf(downloads), sb);
			}
		}
	}

	protected void appendMetadata(String i18nKey, String value, StringBuilder sb) {
		sb.append(translate(i18nKey)).append(": ").append(value).append('\n');
	}

	protected void appendPublicationDate(VFSMetadata infos, StringBuilder sb) {
		String[] publicationDate = infos.getPublicationDate();
		if (publicationDate == null || publicationDate.length != 2)
			return;
		String month = publicationDate[1];
		String year = publicationDate[0];
		if (StringHelper.containsNonWhitespace(month) || StringHelper.containsNonWhitespace(year)) {
			sb.append(translate("mf.publishDate")).append(":");
			if (StringHelper.containsNonWhitespace(month)) {
				sb.append(" ").append(translate("mf.month").replaceAll(" ", "")).append(" ").append(month);
			}
			if (StringHelper.containsNonWhitespace(year)) {
				sb.append(" ").append(translate("mf.year").replaceAll(" ", "")).append(" ").append(year);
			}
			sb.append('\n');
		}
	}

	protected void appendBusinessPath(VFSContainer rootContainer, VFSLeaf file, StringBuilder sb) {
		BusinessControlFactory bCF = BusinessControlFactory.getInstance();
		String businnessPath = getWindowControl().getBusinessControl().getAsString();

		String relPath = getRelativePath(rootContainer, file);
		businnessPath += "[path=" + relPath + "]";

		List<ContextEntry> ces = bCF.createCEListFromString(businnessPath);
		String uri = bCF.getAsURIString(ces, true);
		this.appendMetadata("mf.url", uri, sb);
	}

	protected String getRelativePath(VFSContainer rootContainer, VFSLeaf file) {
		String sb = "/" + file.getName();
		VFSContainer parent = file.getParentContainer();
		while (parent != null && !rootContainer.isSame(parent)) {
			sb = "/" + parent.getName() + sb;
			parent = parent.getParentContainer();
		}
		return sb;
	}

	@Override
	protected boolean validateFormLogic(UserRequest ureq) {
		boolean allOk = super.validateFormLogic(ureq);

		String subject = subjectElement.getValue();
		subjectElement.clearError();
		if (!StringHelper.containsNonWhitespace(subject)) {
			subjectElement.setErrorKey("form.legende.mandatory", null);
			allOk &= false;
		} else if(subject != null && subject.length() > subjectElement.getMaxLength()) {
			subjectElement.setErrorKey("text.element.error.notlongerthan",
					new String[]{ Integer.toString(subjectElement.getMaxLength()) });
			allOk &= false;
		}

		String body = bodyElement.getValue();
		bodyElement.clearError();
		if (!StringHelper.containsNonWhitespace(body)) {
			bodyElement.setErrorKey("form.legende.mandatory", null);
			allOk &= false;
		}

		List<Identity> invalidTos = getInvalidToAddressesFromTextBoxList();
		userListBox.clearError();
		if (!invalidTos.isEmpty()) {
			String[] invalidTosArray = new String[invalidTos.size()];
			userListBox.setErrorKey("mailhelper.error.addressinvalid", invalidTos.toArray(invalidTosArray));
			allOk &= false;
		} else if(toValues == null || toValues.isEmpty()) {
			userListBox.setErrorKey("form.legende.mandatory", null);
			allOk &= false;
		}

		return allOk;
	}

	/**
	 * returns a list of invalid Values within the textboxlist.
	 * values are either email-addresses (manually added, thus external) or
	 * usernames (from autocompletion, thus olat users)
	 *
	 * @return
	 */
	private List<Identity> getInvalidToAddressesFromTextBoxList() {
		List<Identity> invalidTos = new ArrayList<>();

		// the toValues are either usernames (from autocompletion, thus OLAT
		// users) or email-addresses (external)
		if (FolderConfig.getSendDocumentToExtern()) {
			for (IdentityWrapper toValue : toValues) {
				Identity id = toValue.getIdentity();
				if (!MailHelper.isValidEmailAddress(id.getUser().getProperty(UserConstants.EMAIL, null))
						&& !securityManager.isIdentityVisible(id)) {
					invalidTos.add(id);
				}
			}
		} else {
			for (IdentityWrapper toValue : toValues) {
				Identity id = toValue.getIdentity();
				if(!securityManager.isIdentityVisible(id)){
					invalidTos.add(id);
				}
			}
		}
		return invalidTos;
	}

	@Override
	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
		if(source == addEmailLink) {
			doAddEmail(ureq);
		} else if(source instanceof FormLink && source.getUserObject() instanceof IdentityWrapper) {
			if(source.getName().startsWith("rm-")) {
				for(Iterator<IdentityWrapper> wrapperIt=toValues.iterator(); wrapperIt.hasNext(); ) {
					IdentityWrapper wrapper = wrapperIt.next();
					if(source.getUserObject().equals(wrapper)) {
						wrapperIt.remove();
					}
				}
			}
			userListBox.setDirty(true);
		}
		super.formInnerEvent(ureq, source, event);
	}

	@Override
	public void event(UserRequest ureq, Controller source, Event event) {
		if(source == emailCalloutCtrl) {
			if (event instanceof SingleIdentityChosenEvent) {
				addIdentity((SingleIdentityChosenEvent)event);
			}
			calloutCtrl.deactivate();
		}
	}

	private void addIdentity(SingleIdentityChosenEvent foundEvent) {
		Identity chosenIdentity = foundEvent.getChosenIdentity();
		if (chosenIdentity != null) {
			addIdentity(chosenIdentity);
		}
		userListBox.setDirty(true);
	}

	private void addIdentity(Identity identity) {
		FormLink rmLink = uifactory.addFormLink("rm-" + CodeHelper.getForeverUniqueID(), " ", null, userListBox, Link.NONTRANSLATED + Link.LINK);
		IdentityWrapper wrapper = new IdentityWrapper(identity, rmLink);
		rmLink.setIconLeftCSS("o_icon o_icon_remove");
		rmLink.setUserObject(wrapper);
		toValues.add(wrapper);
		userListBox.setDirty(true);
	}

	@Override
	protected void formOK(UserRequest ureq) {
		List<Identity> tos = new ArrayList<>(toValues.size());
		for(IdentityWrapper wrapper:toValues) {
			tos.add(wrapper.getIdentity());
		}
		String subject = subjectElement.getValue();
		String body = bodyElement.getValue();
		sendEmail(tos, subject, body, ureq);
		fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED);
	}

	@Override
	protected void formCancelled(UserRequest ureq) {
		fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED);
	}

	protected void doAddEmail(UserRequest ureq) {
		String title = translate("add.email");
		removeAsListenerAndDispose(emailCalloutCtrl);
		boolean allowExtern = FolderConfig.getSendDocumentToExtern();
		emailCalloutCtrl = new EMailCalloutCtrl(ureq, getWindowControl(), allowExtern);
		listenTo(emailCalloutCtrl);

		removeAsListenerAndDispose(calloutCtrl);
		calloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), emailCalloutCtrl.getInitialComponent(), addEmailLink, title, true, null);
		listenTo(calloutCtrl);
		calloutCtrl.activate();
	}

	protected void sendEmail(List<Identity> tos, String subject, String body, UserRequest ureq) {
		File[] attachmentArray = null;
		if (attachments != null && !attachments.isEmpty() && allowAttachments) {
			attachmentArray = attachments.toArray(new File[attachments.size()]);
		}

		MailerResult result = new MailerResult();
		String metaId = UUID.randomUUID().toString().replace("-", "");
		for(Identity to:tos) {
			MailBundle bundle = new MailBundle();
			bundle.setToId(to);
			bundle.setMetaId(metaId);
			bundle.setFromId(ureq.getIdentity());
			bundle.setContent(subject, body, attachmentArray);
			result.append(mailManager.sendMessage(bundle));
		}

		Roles roles = ureq.getUserSession().getRoles();
		boolean detailedErrorOutput = roles.isAdministrator() || roles.isSystemAdmin();
		MailHelper.printErrorsAndWarnings(result, getWindowControl(), detailedErrorOutput, ureq.getLocale());
	}

	public class FileInfo {
		private final String filename;
		private final String sizeInMB;
		private final String cssClass;

		public FileInfo(String filename, String sizeInMB, String cssClass) {
			this.filename = filename;
			this.sizeInMB = sizeInMB;
			this.cssClass = cssClass;
		}

		public String getFilename() {
			return filename;
		}

		public String getSizeInMB() {
			return sizeInMB;
		}

		public String getCssClass() {
			return cssClass;
		}
	}

	public final class IdentityWrapper {
		private Identity identity;
		private FormLink removeLink;

		public IdentityWrapper(Identity identity, FormLink removeLink) {
			this.identity = identity;
			this.removeLink = removeLink;
		}

		public String getName() {
			if(identity instanceof EMailIdentity) {
				return identity.getUser().getProperty(UserConstants.EMAIL, null);
			}
			return userManager.getUserDisplayName(identity);
		}

		public Identity getIdentity() {
			return identity;
		}

		public String getRemoveLinkName() {
			return removeLink.getComponent().getComponentName();
		}
	}
}

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】此代码存在路径遍历漏洞，因为`FileSelection.parse`方法直接将用户请求中的文件路径参数转换为文件列表，而没有任何验证。攻击者可以利用此漏洞通过提交精心设计的文件路径参数来读取或操作文件系统中的任意文件。
【关键代码模式】`FileSelection.parse`方法，`UserRequest.getHttpReq().getParameterValues(FORM_ID)`，`FileUtils.validateFilename(fileName)`
【修复方法说明】修复方法通过验证用户提交的文件路径是否存在于当前容器中来解决问题，从而防止路径遍历攻击。
【最佳实践】使用白名单验证用户输入，确保所有文件路径都在预期的范围内。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/edu/stanford/nlp/ie/machinereading/common/DomReader.java
### 代码内容：
package edu.stanford.nlp.ie.machinereading.common;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Generic DOM reader for an XML file
 */
public class DomReader {

	/**
	 * Searches (recursively) for the first child that has the given name
	 */
	protected static Node getChildByName(Node node, String name) {
		NodeList children = node.getChildNodes();

		// this node matches
		if (node.getNodeName().equals(name))
			return node;

		// search children
		for (int i = 0; i < children.getLength(); i++) {
			Node found = getChildByName(children.item(i), name);
			if (found != null)
				return found;
		}

		// failed
		return null;
	}

	/**
	 * Searches for all immediate children with the given name
	 */
	protected static List<Node> getChildrenByName(Node node, String name) {
		List<Node> matches = new ArrayList<>();
		NodeList children = node.getChildNodes();

		// search children
		for (int i = 0; i < children.getLength(); i++) {
			Node child = children.item(i);
			if (child.getNodeName().equals(name)) {
				matches.add(child);
			}
		}

		return matches;
	}

	/**
	 * Searches for children that have the given attribute
	 */
	protected static Node getChildByAttribute(Node node, String attributeName, String attributeValue) {
		NodeList children = node.getChildNodes();
		NamedNodeMap attribs = node.getAttributes();
		Node attribute = null;

		// this node matches
		if (attribs != null && (attribute = attribs.getNamedItem(attributeName)) != null
				&& attribute.getNodeValue().equals(attributeValue))
			return node;

		// search children
		for (int i = 0; i < children.getLength(); i++) {
			Node found = getChildByAttribute(children.item(i), attributeName, attributeValue);
			if (found != null)
				return found;
		}

		// failed
		return null;
	}

	/**
	 * Searches for children that have the given name and attribute
	 */
	protected static Node getChildByNameAndAttribute(Node node, String name, String attributeName, String attributeValue) {
		NodeList children = node.getChildNodes();
		NamedNodeMap attribs = node.getAttributes();
		Node attribute = null;

		// this node matches
		if (node.getNodeName().equals(name) && attribs != null
				&& (attribute = attribs.getNamedItem(attributeName)) != null
				&& attribute.getNodeValue().equals(attributeValue))
			return node;

		// search children
		for (int i = 0; i < children.getLength(); i++) {
			Node found = getChildByAttribute(children.item(i), attributeName, attributeValue);
			if (found != null)
				return found;
		}

		// failed
		return null;
	}

	/**
	 * Fetches the value of a given attribute
	 */
	public static String getAttributeValue(Node node, String attributeName) {
		try {
			return node.getAttributes().getNamedItem(attributeName).getNodeValue();
		} catch (Exception e) {
		}

		return null;
	}

	/**
	 * Constructs one Document from an XML file
	 */
	public static Document readDocument(File f) throws IOException, SAXException, ParserConfigurationException {
		Document document = null;

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		// factory.setValidating(true);
		// factory.setNamespaceAware(true);

		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.parse(f);

			// displayDocument(document);

		} catch (SAXException sxe) {
			// Error generated during parsing)
			Exception x = sxe;
			if (sxe.getException() != null)
				x = sxe.getException();
			x.printStackTrace();
			throw sxe;
		} catch (ParserConfigurationException pce) {
			// Parser with specified options can't be built
			pce.printStackTrace();
			throw pce;
		} catch (IOException ioe) {
			// I/O error
			ioe.printStackTrace();
			throw ioe;
		}

		return document;
	} // readDocument
}

## 内容2
### 文件路径：
src/edu/stanford/nlp/semgraph/semgrex/ssurgeon/Ssurgeon.java
### 代码内容：
package edu.stanford.nlp.semgraph.semgrex.ssurgeon;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.text.DateFormat;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import edu.stanford.nlp.util.StringUtils;
import edu.stanford.nlp.util.logging.RedwoodConfiguration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import edu.stanford.nlp.trees.GrammaticalRelation;
import edu.stanford.nlp.semgraph.SemanticGraph;
import edu.stanford.nlp.semgraph.SemanticGraphUtils;
import edu.stanford.nlp.semgraph.semgrex.ssurgeon.pred.*;
import edu.stanford.nlp.semgraph.semgrex.SemgrexPattern;
import edu.stanford.nlp.util.Generics;
import edu.stanford.nlp.util.logging.Redwood;

/**
 * This is the primary class for loading and saving out Ssurgeon patterns.
 * This is also the class that maintains the current list of resources loaded into Ssurgeon: any pattern
 * loaded can reference these resources.
 *
 * @author Eric Yeh
 */
public class Ssurgeon  {

  private static final boolean VERBOSE = false;

  // singleton, to ensure all use the same resources
  private static Ssurgeon instance = null;

  private Ssurgeon() {}

  public static Ssurgeon inst() {
    synchronized(Ssurgeon.class) {
      if (instance == null)
        instance = new Ssurgeon();
    }
    return instance;
  }

  // Logging to file facilities.
  // The prefix is used to append stuff in front of the logging messages
  /** A logger for this class */
  private static final Redwood.RedwoodChannels log = Redwood.channels(Ssurgeon.class);

  private String logPrefix = null;
  public void initLog(File logFilePath) throws IOException {
    RedwoodConfiguration.empty()
      .handlers(RedwoodConfiguration.Handlers.chain(
        RedwoodConfiguration.Handlers.showAllChannels(), RedwoodConfiguration.Handlers.stderr),
        RedwoodConfiguration.Handlers.file(logFilePath.toString())
      ).apply();
    // fh.setFormatter(new NewlineLogFormatter());

    System.out.println("Starting Ssurgeon log, at "+logFilePath.getAbsolutePath()+" date=" + DateFormat.getDateInstance(DateFormat.FULL).format(new Date()));
    log.info("Starting Ssurgeon log, date=" + DateFormat.getDateInstance(DateFormat.FULL).format(new Date()));
  }

  public void setLogPrefix(String logPrefix) {
    this.logPrefix = logPrefix;
  }

  /**
   * Given a list of SsurgeonPattern edit scripts, and a SemanticGraph
   * to operate over, returns a list of expansions of that graph, with
   * the result of each edit applied against a copy of the graph.
   */
  public  List<SemanticGraph> expandFromPatterns(List<SsurgeonPattern> patternList, SemanticGraph sg) throws Exception {
    List<SemanticGraph> retList = new ArrayList<>();
    for (SsurgeonPattern pattern :patternList) {
      Collection<SemanticGraph> generated = pattern.execute(sg);
      for (SemanticGraph orderedGraph : generated) {
        //orderedGraph.vertexList(true);
        //orderedGraph.edgeList(true);
        retList.add(orderedGraph);
        System.out.println("\ncompact = "+orderedGraph.toCompactString());
        System.out.println("regular=" + orderedGraph);
      }

      if (generated.size() > 0) {
        if (log != null) {
          log.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *");
          log.info("Pre remove duplicates, num="+generated.size());
        }
        SemanticGraphUtils.removeDuplicates(generated, sg);
        if (log != null) {
          log.info("Expand from patterns");
          if (logPrefix != null) log.info(logPrefix);
          log.info("Pattern = '"+pattern.getUID()+"' generated "+generated.size()+" matches");
          log.info("= = = = = = = = = =\nSrc graph:\n" + sg + "\n= = = = = = = = = =\n");
          int index=1;
          for (SemanticGraph genSg : generated) {
            log.info("REWRITE "+(index++));
            log.info(genSg.toString());
            log.info(". . . . .\n");
          }
        }
      }
    }
    return retList;
  }

  /**
   * Similar to the expandFromPatterns, but performs an exhaustive
   * search, performing simplifications on the graphs until exhausted.
   *
   * TODO: ensure cycles do not occur
   * NOTE: put in an arbitrary depth limit of 3, to prevent churning way too much (heuristic)
   *
   */
  public  Collection<SemanticGraph> exhaustFromPatterns(List<SsurgeonPattern> patternList, SemanticGraph sg) throws Exception {
    Collection<SemanticGraph> generated = exhaustFromPatterns(patternList, sg, 1);
    if (generated.size() > 1) {
      if (log != null)
        log.info("Before remove dupe, size="+generated.size());
      generated = SemanticGraphUtils.removeDuplicates(generated, sg);
      if (log != null)
        log.info("AFTER remove dupe, size="+generated.size());
    }
    return generated;
  }
  private  List<SemanticGraph> exhaustFromPatterns(List<SsurgeonPattern> patternList, SemanticGraph sg, int depth) throws Exception {
    List<SemanticGraph> retList = new ArrayList<>();
    for (SsurgeonPattern pattern : patternList) {
      Collection<SemanticGraph> generated = pattern.execute(sg);
      for (SemanticGraph modGraph : generated) {
        //modGraph = SemanticGraphUtils.resetVerticeOrdering(modGraph);
        //modGraph.vertexList(true);
        //modGraph.edgeList(true);
        retList.add(modGraph);
      }

      if (log != null && generated.size() > 0) {
        log.info("* * * * * * * * * ** * * * * * * * * *");
        log.info("Exhaust from patterns, depth="+depth);
        if (logPrefix != null) log.info(logPrefix);
        log.info("Pattern = '"+pattern.getUID()+"' generated "+generated.size()+" matches");
        log.info("= = = = = = = = = =\nSrc graph:\n"+sg.toString()+"\n= = = = = = = = = =\n");
        int index=1;
        for (SemanticGraph genSg : generated) {
          log.info("REWRITE "+(index++));
          log.info(genSg.toString());
          log.info(". . . . .\n");
        }
      }
    }

    if (retList.size() > 0) {
      List<SemanticGraph> referenceList = new ArrayList<>(retList);
      for (SemanticGraph childGraph : referenceList) {
        if (depth < 3)
          retList.addAll(exhaustFromPatterns(patternList, childGraph, depth + 1));
      }
    }
    return retList;
  }

  /**
   * Given a path to a file, converts it into a SsurgeonPattern
   * TODO: finish implementing this stub.
   */
  public static SsurgeonPattern getOperationFromFile(String path) {
    return null;
  }

  //
  // Resource management
  //
  private Map<String, SsurgeonWordlist> wordListResources = Generics.newHashMap();

  /**
   * Places the given word list resource under the given ID.
   * Note: can overwrite existing one in place.
   */
  private void addResource(SsurgeonWordlist resource) {
    wordListResources.put(resource.getID(), resource);
  }

  /**
   * Returns the given resource with the id.
   * If does not exist, will throw exception.
   */
  public SsurgeonWordlist getResource(String id) {
    return wordListResources.get(id);
  }

  public Collection<SsurgeonWordlist> getResources() {
    return wordListResources.values();
  }

  public static final String GOV_NODENAME_ARG = "-gov";
  public static final String DEP_NODENAME_ARG = "-dep";
  public static final String EDGE_NAME_ARG = "-edge";
  public static final String NODENAME_ARG = "-node";
  public static final String RELN_ARG = "-reln";
  public static final String NODE_PROTO_ARG = "-nodearg";
  public static final String WEIGHT_ARG = "-weight";
  public static final String NAME_ARG = "-name";

  // args for Ssurgeon edits, allowing us to not
  // worry about arg order (and to make things appear less confusing)
  protected static class SsurgeonArgs {
    // Below are values keyed by Semgrex name
    public String govNodeName = null;

    public String dep = null;

    public String edge = null;

    public String reln = null;

    public String node = null;

    // below are string representations of the intended values
    public String nodeString = null;

    public double weight = 1.0;

    public String name = null;
  }

  /**
   * This is a specialized args parser, as we want to split on
   * whitespace, but retain everything inside quotes, so we can pass
   * in hashmaps in String form.
   */
  private static String[] parseArgs(String argsString) {
    List<String> retList = new ArrayList<>();
    String patternString = "(?:[^\\s\\\"]++|\\\"[^\\\"]*+\\\"|(\\\"))++";
    Pattern pattern = Pattern.compile(patternString);
    Matcher matcher = pattern.matcher(argsString);
    while (matcher.find()) {
      if (matcher.group(1) == null) {
        String matched = matcher.group();
        if (matched.charAt(0) == '"' &&
            matched.charAt(matched.length()-1) == '"')
          retList.add(matched.substring(1, matched.length()-1));
        else
          retList.add(matched);
      }  else
        throw new IllegalArgumentException("Unmatched quote in string to parse");
    }
    return retList.toArray(StringUtils.EMPTY_STRING_ARRAY);
  }

  /**
   * Given a string entry, converts it into a SsurgeonEdit object.
   */
  public static SsurgeonEdit parseEditLine(String editLine) {
    // Extract the operation name first
    String[] tuples1 = editLine.split("\\s+", 2);
    if (tuples1.length < 2) {
      throw new IllegalArgumentException("Error in SsurgeonEdit.parseEditLine: invalid number of arguments");
    }
    String command = tuples1[0];
    String[] argsArray = parseArgs(tuples1[1]);
    SsurgeonArgs argsBox = new SsurgeonArgs();

    for (int argIndex = 0; argIndex < argsArray.length; ++argIndex) {
      switch (argsArray[argIndex]) {
        case GOV_NODENAME_ARG:
          argsBox.govNodeName = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        case DEP_NODENAME_ARG:
          argsBox.dep = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        case EDGE_NAME_ARG:
          argsBox.edge = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        case RELN_ARG:
          argsBox.reln = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        case NODENAME_ARG:
          argsBox.node = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        case NODE_PROTO_ARG:
          argsBox.nodeString = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        case WEIGHT_ARG:
          argsBox.weight = Double.valueOf(argsArray[argIndex + 1]);
          argIndex += 2;
          break;
        case NAME_ARG:
          argsBox.name = argsArray[argIndex + 1];
          argIndex += 2;
          break;
        default:
          throw new IllegalArgumentException("Parsing Ssurgeon args: unknown flag " + argsArray[argIndex]);
      }
    }

    // Parse the arguments based upon the type of command to execute.
    // TODO: this logic really should be moved into the individual classes.  The string-->class
    // mappings should also be stored in more appropriate data structure.
    SsurgeonEdit retEdit;
    if (command.equalsIgnoreCase(AddDep.LABEL)) {
      retEdit = AddDep.createEngAddDep(argsBox.govNodeName, argsBox.reln, argsBox.nodeString);
    } else if (command.equalsIgnoreCase(AddNode.LABEL)) {
      retEdit = AddNode.createAddNode(argsBox.nodeString, argsBox.name);
    } else if (command.equalsIgnoreCase(AddEdge.LABEL)) {
      retEdit = AddEdge.createEngAddEdge(argsBox.govNodeName, argsBox.dep, argsBox.reln);
    } else if (command.equalsIgnoreCase(DeleteGraphFromNode.LABEL)) {
      retEdit = new DeleteGraphFromNode(argsBox.node);
    } else if (command.equalsIgnoreCase(RemoveEdge.LABEL)) {
      retEdit = new RemoveEdge(GrammaticalRelation.valueOf(argsBox.reln), argsBox.govNodeName, argsBox.dep);
    } else if (command.equalsIgnoreCase(RemoveNamedEdge.LABEL)) {
      retEdit = new RemoveNamedEdge(argsBox.edge, argsBox.govNodeName, argsBox.dep);
    } else if (command.equalsIgnoreCase(SetRoots.LABEL)) {
      String[] names = tuples1[1].split("\\s+");
      List<String> newRoots = Arrays.asList(names);
      retEdit = new SetRoots(newRoots);
    } else if (command.equalsIgnoreCase(KillNonRootedNodes.LABEL)) {
      retEdit = new KillNonRootedNodes();
    } else if (command.equalsIgnoreCase(KillAllIncomingEdges.LABEL)) {
      retEdit = new KillAllIncomingEdges(argsBox.node);
    } else {
      throw new IllegalArgumentException("Error in SsurgeonEdit.parseEditLine: command '"+command+"' is not supported");
    }
    return retEdit;
  }

  //public static SsurgeonPattern fromXML(String xmlString) throws Exception {
  //SAXBuilder builder = new SAXBuilder();
  //Document jdomDoc = builder.build(xmlString);
  //jdomDoc.getRootElement().getChildren(SsurgeonPattern.SSURGEON_ELEM_TAG);
  //}

  /**
   * Given a target filepath and a list of Ssurgeon patterns, writes them out as XML forms.
   */
  public static void writeToFile(File tgtFile, List<SsurgeonPattern> patterns) {
    try {
      Document domDoc = createPatternXMLDoc(patterns);
      if (domDoc != null) {
        Transformer tformer = TransformerFactory.newInstance().newTransformer();
        tformer.setOutputProperty(OutputKeys.INDENT, "yes");
        tformer.transform(new DOMSource(domDoc), new StreamResult(tgtFile));
      } else {
        log.warning("Was not able to create XML document for pattern list, file not written.");
      }
    } catch (Exception e) {
      log.error(Ssurgeon.class.getName(), "writeToFile");
      log.error(e);
    }
  }

  public static String writeToString(SsurgeonPattern pattern) {
    try {
      List<SsurgeonPattern> patterns = new LinkedList<>();
      patterns.add(pattern);
      Document domDoc = createPatternXMLDoc(patterns);
      if (domDoc != null) {
        Transformer tformer = TransformerFactory.newInstance().newTransformer();
        tformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter sw = new StringWriter();
        tformer.transform(new DOMSource(domDoc), new StreamResult(sw));
        return sw.toString();
      } else {
        log.warning("Was not able to create XML document for pattern list.");
      }
    } catch (Exception e) {
      log.info("Error in writeToString, could not process pattern="+pattern);
      log.info(e);
      return null;
    }
    return "";
  }

  private static Document createPatternXMLDoc(List<SsurgeonPattern> patterns) {
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document domDoc = db.newDocument();
      Element rootElt = domDoc.createElement(SsurgeonPattern.ELT_LIST_TAG);
      domDoc.appendChild(rootElt);
      int ordinal = 1;
      for (SsurgeonPattern pattern : patterns) {
        Element patElt = domDoc.createElement(SsurgeonPattern.SSURGEON_ELEM_TAG);
        patElt.setAttribute(SsurgeonPattern.ORDINAL_ATTR, String.valueOf(ordinal));
        Element semgrexElt = domDoc.createElement(SsurgeonPattern.SEMGREX_ELEM_TAG);
        semgrexElt.appendChild(domDoc.createTextNode(pattern.getSemgrexPattern().pattern()));
        patElt.appendChild(semgrexElt);
        Element uidElem = domDoc.createElement(SsurgeonPattern.UID_ELEM_TAG);
        uidElem.appendChild(domDoc.createTextNode(pattern.getUID()));
        patElt.appendChild(uidElem);
        Element notesElem = domDoc.createElement(SsurgeonPattern.NOTES_ELEM_TAG);
        notesElem.appendChild(domDoc.createTextNode(pattern.getNotes()));
        patElt.appendChild(notesElem);

        SemanticGraph semgrexGraph = pattern.getSemgrexGraph();
        if (semgrexGraph != null) {
          Element patNode = domDoc.createElement(SsurgeonPattern.SEMGREX_GRAPH_ELEM_TAG);
          patNode.appendChild(domDoc.createTextNode(semgrexGraph.toCompactString()));
        }
        Element editList = domDoc.createElement(SsurgeonPattern.EDIT_LIST_ELEM_TAG);
        patElt.appendChild(editList);
        int editOrdinal = 1;
        for (SsurgeonEdit edit : pattern.getEditScript()) {
          Element editElem = domDoc.createElement(SsurgeonPattern.EDIT_ELEM_TAG);
          editElem.setAttribute(SsurgeonPattern.ORDINAL_ATTR, String.valueOf(editOrdinal));
          editElem.appendChild(domDoc.createTextNode(edit.toEditString()));
          editList.appendChild(editElem);
          editOrdinal++;
        }
        rootElt.appendChild(patElt);
        ordinal++;
      }
      return domDoc;
    } catch (Exception e) {
      log.error(Ssurgeon.class.getName(), "createPatternXML");
      log.error(e);
      return null;
    }
  }

  /**
   * Given a path to a file containing a list of SsurgeonPatterns, returns
   *
   * TODO: deal with resources
   * @throws Exception
   */
  @SuppressWarnings("unchecked")
  public List<SsurgeonPattern> readFromFile(File file) throws Exception {
    List<SsurgeonPattern> retList = new ArrayList<>();
    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);

    if (VERBOSE)
      System.out.println("Reading ssurgeon file="+file.getAbsolutePath());

    NodeList patternNodes = doc.getElementsByTagName(SsurgeonPattern.SSURGEON_ELEM_TAG);
    for (int i=0; i<patternNodes.getLength(); i++) {
      Node node = patternNodes.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        Element elt = (Element) node;
        SsurgeonPattern pattern = ssurgeonPatternFromXML(elt);
        retList.add(pattern);
      }
    }

    NodeList resourceNodes = doc.getElementsByTagName(SsurgeonPattern.RESOURCE_TAG);
    for (int i=0; i < resourceNodes.getLength(); i++) {
      Node node = patternNodes.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        Element resourceElt = (Element) node;
        SsurgeonWordlist wlRsrc = new SsurgeonWordlist(resourceElt);
        addResource(wlRsrc);
      }
    }

    return retList;
  }

  /**
   * Reads all Ssurgeon patterns from file.
   * @throws Exception
   */
  public List<SsurgeonPattern> readFromDirectory(File dir) throws Exception {
    if (!dir.isDirectory()) throw new Exception("Given path not a directory, path="+dir.getAbsolutePath());
    if (VERBOSE)
      System.out.println("Reading Ssurgeon patterns from directory = "+dir.getAbsolutePath());
    File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".xml"));
    List<SsurgeonPattern> patterns = new ArrayList<>();
    for (File file : files) {
      try {
        patterns.addAll(readFromFile(file));
      } catch (Exception e) {
        log.error(e);
      }
    }
    return patterns;
  }

  /**
   * Given the root Element for a SemgrexPattern (SSURGEON_ELEM_TAG), converts
   * it into its corresponding SemgrexPattern object.
   * @throws Exception
   */
  @SuppressWarnings("unchecked")
  public static SsurgeonPattern ssurgeonPatternFromXML(Element elt) throws Exception {
    String uid = getTagText(elt, SsurgeonPattern.UID_ELEM_TAG);
    String notes = getTagText(elt, SsurgeonPattern.NOTES_ELEM_TAG);
    String semgrexString = getTagText(elt, SsurgeonPattern.SEMGREX_ELEM_TAG);
    SemgrexPattern semgrexPattern = SemgrexPattern.compile(semgrexString);
    SsurgeonPattern retPattern = new SsurgeonPattern(uid, semgrexPattern);
    retPattern.setNotes(notes);
    NodeList editNodes = elt.getElementsByTagName(SsurgeonPattern.EDIT_LIST_ELEM_TAG);
    for (int i=0; i<editNodes.getLength(); i++) {
      Node node = editNodes.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        Element editElt = (Element) node;
        String editVal = getEltText(editElt);
        retPattern.addEdit(Ssurgeon.parseEditLine(editVal));
      }
    }

    // If predicate available, parse
    Element predElt = getFirstTag(elt, SsurgeonPattern.PREDICATE_TAG);
    if (predElt != null) {
      SsurgPred pred = assemblePredFromXML(getFirstChildElement(predElt));
      retPattern.setPredicate(pred);
    }
    return retPattern;
  }

  /**
   * Constructs a {@code SsurgPred} structure from file, given the root element.
   * @throws Exception
   */
  public static SsurgPred assemblePredFromXML(Element elt) throws Exception {
    String eltName = elt.getTagName();
    switch (eltName) {
      case SsurgeonPattern.PREDICATE_AND_TAG:
        SsurgAndPred andPred = new SsurgAndPred();
        for (Element childElt : getChildElements(elt)) {
          SsurgPred childPred = assemblePredFromXML(childElt);
          andPred.add(childPred);
          return andPred;
        }
        break;
      case SsurgeonPattern.PREDICATE_OR_TAG:
        SsurgOrPred orPred = new SsurgOrPred();
        for (Element childElt : getChildElements(elt)) {
          SsurgPred childPred = assemblePredFromXML(childElt);
          orPred.add(childPred);
          return orPred;
        }
        break;
      case SsurgeonPattern.PRED_WORDLIST_TEST_TAG:
        String id = elt.getAttribute(SsurgeonPattern.PRED_ID_ATTR);
        String resourceID = elt.getAttribute("resourceID");
        String typeStr = elt.getAttribute("type");
        String matchName = getEltText(elt).trim(); // node name to match on

        if (matchName == null) {
          throw new Exception("Could not find match name for " + elt);
        }
        if (id == null) {
          throw new Exception("No ID attribute for element = " + elt);
        }
        return new WordlistTest(id, resourceID, typeStr, matchName);
    }

    // Not a valid node, error out!
    throw new Exception("Invalid node encountered during Ssurgeon predicate processing, node name="+eltName);
  }

  /**
   * Reads in the test file and prints readable to string (for debugging).
   * Input file consists of semantic graphs, in compact form.
   */
  public void testRead(File tgtDirPath) throws Exception {
    List<SsurgeonPattern> patterns = readFromDirectory(tgtDirPath);

    System.out.println("Patterns, num = "+patterns.size());
    int num = 1;
    for (SsurgeonPattern pattern : patterns) {
      System.out.println("\n# "+(num++));
      System.out.println(pattern);
    }

    System.out.println("\n\nRESOURCES ");
    for (SsurgeonWordlist rsrc : inst().getResources()) {
      System.out.println(rsrc+"* * * * *");
    }

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    boolean runFlag = true;
    Ssurgeon.inst().initLog(new File("./ssurgeon_run.log"));
    while (runFlag) {
      try {
        System.out.println("Enter a sentence:");
        String line = in.readLine();
        if (line.isEmpty()) {
          System.exit(0);
        }
        System.out.println("Parsing...");
        SemanticGraph sg = SemanticGraph.valueOf(line);
        System.out.println("Graph = "+sg);
        Collection<SemanticGraph> generated = Ssurgeon.inst().exhaustFromPatterns(patterns, sg);
        System.out.println("# generated = "+generated.size());
        int index = 1;
        for (SemanticGraph gsg : generated) {
          System.out.println("\n# "+index);
          System.out.println(gsg);
          index++;
        }
      } catch (Exception e) {
        log.error(e);
      }
    }
  }

  /*
   * XML convenience routines
   */
  // todo [cdm 2016]: Aren't some of these methods available as generic XML methods elsewhere??

  /**
   * For the given element, returns the text for the first child Element with
   * the given tag.
   */
  public static String getTagText(Element element, String tag) {
    try {
      // From root element, identify first with tag, then find the
      // first child under that, which we treat as a TEXT node.
      Element firstElt = getFirstTag(element, tag);
      if (firstElt == null) return "";
      return getEltText(firstElt);
    } catch (Exception e) {
      log.warning("Exception thrown attempting to get tag text for tag="+tag+", from element="+element);
    }
    return "";
  }

  /**
   * For a given Element, treats the first child as a text element
   * and returns its value.
   */
  public static String getEltText(Element element) {
    try {
      NodeList childNodeList = element.getChildNodes();
      if (childNodeList.getLength() == 0) return "";
      return childNodeList.item(0).getNodeValue();
    } catch (Exception e) {
      log.warning("Exception e=" + e.getMessage() + " thrown calling getEltText on element=" + element);
    }
    return "";
  }

  /**
   * For the given element, finds the first child Element with the given tag.
   */
  private static Element getFirstTag(Element element, String tag) {
    try {
      NodeList nodeList = element.getElementsByTagName(tag);
      if (nodeList.getLength() == 0) return null;
      for (int i=0; i < nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE)
          return (Element) node;
      }
    } catch (Exception e) {
      log.warning("Error getting first tag "+tag+" under element="+element);
    }
    return null;
  }

  /**
   * Returns the first child whose node type is Element under the given Element.
   */
  private static Element getFirstChildElement(Element element) {
    try {
      NodeList nodeList = element.getChildNodes();
      for (int i=0; i<nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE)
          return (Element) node;
      }
    } catch (Exception e) {
      log.warning("Error getting first child Element for element=" + element+", exception=" + e);
    }
    return null;
  }

  /**
   * Returns all of the Element typed children from the given element.  Note: disregards
   * other node types.
   */
  private static List<Element> getChildElements(Element element) {
    LinkedList<Element> childElements = new LinkedList<>();
    try {
      NodeList nodeList = element.getChildNodes();
      for (int i=0; i<nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
          childElements.add((Element) node);
        }
      }
    } catch (Exception e) {
      log.warning("Exception thrown getting all children for element=" + element+ ", e=" + e);
    }
    return childElements;
  }

  /*
   * Main class evocation stuff
   */

  public enum RUNTYPE {
    interactive, // interactively test contents of pattern directory against entered sentences
    testinfo // test against a given infofile (RTE), generating rewrites for hypotheses
  }

  public static class ArgsBox {
    public RUNTYPE type = RUNTYPE.interactive;

    public String patternDirStr = null;
    public File patternDir = null;

    public String info = null;
    public File infoPath = null;

    public void init() {
      patternDir = new File(patternDirStr);
      if (type == RUNTYPE.testinfo)
        infoPath = new File(info);
    }

    @Override
    public String toString() {
      StringWriter buf = new StringWriter();
      buf.write("type ="+type+"\n");
      buf.write("pattern dir = "+patternDir.getAbsolutePath());
      if (type == RUNTYPE.testinfo) {
        buf.write("info file = "+info);
        if (info != null)
          buf.write(", path = "+infoPath.getAbsolutePath());
      }
      return buf.toString();
    }
  }

  protected static ArgsBox argsBox = new ArgsBox();

  /**
   * Performs a simple test and print of a given file.
   * Usage Ssurgeon [-info infoFile] -patterns patternDir [-type interactive|testinfo]
   */
  public static void main(String[] args) {
    for (int argIndex = 0; argIndex < args.length; ++argIndex) {
      if (args[argIndex].equalsIgnoreCase("-info")) {
        argsBox.info = args[argIndex + 1];
        argIndex += 2;
      } else if (args[argIndex].equalsIgnoreCase("-patterns")) {
        argsBox.patternDirStr = args[argIndex + 1];
        argIndex += 2;
      } else if (args[argIndex].equalsIgnoreCase("-type")) {
        argsBox.type = RUNTYPE.valueOf(args[argIndex + 1]);
        argIndex += 2;
      }
    }
    if (argsBox.patternDirStr == null) {
      throw new IllegalArgumentException("Need to give a pattern location with -patterns");
    }
    argsBox.init();

    System.out.println(argsBox);
    try {
      if (argsBox.type == RUNTYPE.interactive) {
        Ssurgeon.inst().testRead(argsBox.patternDir);
      }
    } catch (Exception e) {
      log.error(e);
    }
  }

}