/**
* Copyright (c) 2009--2015 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.frontend.action.configuration;
import com.redhat.rhn.common.conf.Config;
import com.redhat.rhn.common.conf.ConfigDefaults;
import com.redhat.rhn.common.util.StringUtil;
import com.redhat.rhn.common.validator.ValidatorError;
import com.redhat.rhn.common.validator.ValidatorResult;
import com.redhat.rhn.domain.config.ConfigChannel;
import com.redhat.rhn.domain.config.ConfigFile;
import com.redhat.rhn.domain.config.ConfigFileType;
import com.redhat.rhn.domain.config.ConfigRevision;
import com.redhat.rhn.domain.role.RoleFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.struts.RequestContext;
import com.redhat.rhn.frontend.struts.ScrubbingDynaActionForm;
import com.redhat.rhn.frontend.struts.StrutsDelegate;
import com.redhat.rhn.manager.configuration.ConfigurationManager;
import com.redhat.rhn.manager.configuration.ConfigurationValidation;
import com.redhat.rhn.manager.configuration.file.BinaryFileData;
import com.redhat.rhn.manager.configuration.file.ConfigFileData;
import com.redhat.rhn.manager.configuration.file.DirectoryData;
import com.redhat.rhn.manager.configuration.file.SymlinkData;
import com.redhat.rhn.manager.configuration.file.TextFileData;
import java.io.BufferedInputStream;
import org.apache.struts.upload.FormFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import javax.servlet.http.HttpServletRequest;
/**
* ConfigFileForm
* @version $Rev$
*/
public class ConfigFileForm extends ScrubbingDynaActionForm {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = -2162768922109257186L;
// configFileForm elements
public static final String REV_PATH = "cffPath";
public static final String REV_SYMLINK_TARGET_PATH = "targetPath";
public static final String REV_UID = "cffUid";
public static final String REV_GID = "cffGid";
public static final String REV_PERMS = "cffPermissions";
public static final String REV_SELINUX_CTX = "cffSELinuxCtx";
public static final String REV_MACROSTART = "cffMacroStart";
public static final String REV_MACROEND = "cffMacroEnd";
public static final String REV_CONTENTS = "contents"; //cffContent
public static final String REV_FILETYPE = "filetype"; //cffFiletype
public static final String REV_BINARY = "binary";
public static final String REV_UPLOAD = "cffUpload";
public static final String REV_EDITABLE = "editable";
public static final String REV_DISPLAYABLE = "displayable";
public static final String REV_TOOLARGE = "toolarge";
public static final String REV_NUMBER = "revnum";
public static final String DEFAULT_CONFIG_DELIM_START = "{|";
public static final String DEFAULT_CONFIG_DELIM_END = "|}";
// This cannot be the right way to find this
// Here to deal with form-size-limits
public static final long MAX_EDITABLE_SIZE;
static {
MAX_EDITABLE_SIZE = new Long(new Config().getInt(
ConfigDefaults.CONFIG_FILE_EDIT_SIZE) * 1024);
}
/**
* Set acceptable defaults for our form
*/
public void setDefaults() {
set(ConfigFileForm.REV_PERMS, "644");
set(ConfigFileForm.REV_UID, "root");
set(ConfigFileForm.REV_GID, "root");
set(ConfigFileForm.REV_FILETYPE, ConfigFileType.FILE);
setBinary(false);
String macroStart = Config.get().getString("web.config_delim_start",
DEFAULT_CONFIG_DELIM_START);
String macroEnd = Config.get().getString("web.config_delim_end",
DEFAULT_CONFIG_DELIM_END);
set(ConfigFileForm.REV_MACROSTART, macroStart);
set(ConfigFileForm.REV_MACROEND, macroEnd);
}
/**
* Validate a file-upload. This checks that:
* <ul>
* <li>The file exists
* <li>The file isn't too large
* <li>If the file is text, its contents are valid after macro-substitution
* </ul>
* @param request the incoming request
* @return a ValidatorResult.. The list is empty if everything is OK
*/
public ValidatorResult validateUpload(HttpServletRequest request) {
ValidatorResult msgs = new ValidatorResult();
FormFile file = (FormFile)get(REV_UPLOAD);
//make sure there is a file
if (file == null ||
file.getFileName() == null ||
file.getFileName().trim().length() == 0) {
msgs.addError(new ValidatorError("error.config-not-specified"));
}
else if (file.getFileSize() == 0) {
msgs.addError(new ValidatorError("error.config-empty",
file.getFileName()));
}
//make sure they didn't send in something huge
else if (file.getFileSize() > ConfigFile.getMaxFileSize()) {
msgs.addError(new ValidatorError("error.configtoolarge",
StringUtil.displayFileSize(ConfigFile.getMaxFileSize(), false)));
}
// It exists and isn't too big - is it text?
else if (!isBinary()) {
try {
String content = new String(file.getFileData());
String startDelim = getString(REV_MACROSTART);
String endDelim = getString(REV_MACROEND);
msgs.append(ConfigurationValidation.validateContent(
content, startDelim, endDelim));
}
catch (Exception e) {
msgs.addError(new ValidatorError("error.fatalupload",
StringUtil.displayFileSize(
ConfigFile.getMaxFileSize(), false)));
}
}
return msgs;
}
/**
* Given the incoming request, fill us in with revision info
* @param request the request
* @param cr the revision we're getting data from
*/
public void updateFromRevision(HttpServletRequest request, ConfigRevision cr) {
RequestContext requestContext = new RequestContext(request);
User u = requestContext.getCurrentUser();
set(ConfigFileForm.REV_PATH, cr.getConfigFile().getConfigFileName().getPath());
if (cr.isSymlink() && cr.getConfigInfo().getTargetFileName() != null) {
set(ConfigFileForm.REV_SYMLINK_TARGET_PATH,
cr.getConfigInfo().getTargetFileName().getPath());
}
set(REV_NUMBER, String.valueOf(
cr.getConfigFile().getLatestConfigRevision().getRevision() + 1));
Long mode = cr.getConfigInfo().getFilemode();
String modeStr = mode == null ? "" :
new DecimalFormat("000").format(mode.longValue());
set(ConfigFileForm.REV_PERMS, modeStr);
set(ConfigFileForm.REV_SELINUX_CTX, cr.getConfigInfo().getSelinuxCtx());
set(ConfigFileForm.REV_UID, cr.getConfigInfo().getUsername());
set(ConfigFileForm.REV_GID, cr.getConfigInfo().getGroupname());
set("submitted", Boolean.TRUE);
if (cr.isFile()) {
set(ConfigFileForm.REV_BINARY, cr.getConfigContent().isBinary());
if (!cr.getConfigContent().isBinary()) {
set(ConfigFileForm.REV_CONTENTS, cr.getConfigContent().getContentsString());
}
Boolean toolarge = cr.getConfigContent().getFileSize().
longValue() > MAX_EDITABLE_SIZE;
request.setAttribute(REV_TOOLARGE, toolarge);
}
ConfigActionHelper.setupRequestAttributes(requestContext, cr.getConfigFile(), cr);
request.setAttribute(REV_DISPLAYABLE, canDisplayContent(cr));
request.setAttribute(REV_EDITABLE, canEditContent(u, cr));
}
/**
* You can DISPLAY content IFF
* - It's not a directory
* - It's not binary
* - It's not "too damn big"
* @param cr file of interest
* @return true IFF the above conditions are true
*/
protected boolean canDisplayContent(ConfigRevision cr) {
return (cr.isFile() &&
!cr.getConfigContent().isBinary() &&
cr.getConfigContent().getFileSize().longValue() < MAX_EDITABLE_SIZE);
}
/**
* You can edit a file IFF:
* - You're a config-admin, and this is a GLOBAL channel
* - You're a system-admin, and this is a LOCAL or SANDBOX channel
* @param user logged-in user making the request
* @param cr revision to be edited
* @return true IFF user has write-access to the file
*/
protected boolean canEditContent(User user, ConfigRevision cr) {
ConfigurationManager mgr = ConfigurationManager.getInstance();
ConfigChannel cc = cr.getConfigFile().getConfigChannel();
if (cc.isGlobalChannel()) {
return (user.hasRole(RoleFactory.CONFIG_ADMIN));
}
return (mgr.accessToChannel(user.getId(), cc.getId()));
}
private boolean isUpload() {
return get(REV_UPLOAD) != null;
}
/**
*
* @return true if content is binary false other wise
*/
private boolean isBinary() {
return Boolean.TRUE.equals(get(REV_BINARY));
}
/**
* Returns the config file type of the content
* @return dir/file...
*/
private ConfigFileType extractFileType() {
String ft = getString(ConfigFileForm.REV_FILETYPE);
return ConfigFileType.lookup(ft);
}
/**
* sets if the file is a binary or text.
* @param isBinary true if this file is a binary
*/
private void setBinary(boolean isBinary) {
set(REV_BINARY, Boolean.valueOf(isBinary));
}
/**
*
* @return true if this holds a dir, returns false if it holds a file..
*/
private boolean isDirectory() {
return ConfigFileType.dir().equals(extractFileType());
}
private String getContents() {
return StringUtil.webToLinux(getString(ConfigFileForm.REV_CONTENTS));
}
/**
*
* @return a ConfigFileData representation of this Form
*/
public ConfigFileData toData() {
ConfigFileData data;
if (isDirectory()) {
data = new DirectoryData();
}
else if (ConfigFileType.symlink().equals(extractFileType())) {
data = new SymlinkData(getString(REV_SYMLINK_TARGET_PATH));
}
else if (isBinary()) {
if (isUpload()) {
FormFile file = (FormFile) get(REV_UPLOAD);
try {
data = new BinaryFileData(new BufferedInputStream(
file.getInputStream()), file.getFileSize());
}
catch (IOException e) {
String msg = "Unable to read the uploaded binary file stream";
throw new RuntimeException(msg, e);
}
}
else {
data = new BinaryFileData(new ByteArrayInputStream(new byte[0]), 0);
}
}
else {
if (isUpload()) {
FormFile file = (FormFile) get(REV_UPLOAD);
StrutsDelegate del = StrutsDelegate.getInstance();
data = new TextFileData(del.extractString(file));
}
else {
data = new TextFileData(getContents());
}
data.setMacroStart(getString(REV_MACROSTART));
data.setMacroEnd(getString(REV_MACROEND));
}
data.setPath(getString(REV_PATH));
data.setGroup(getString(REV_GID));
data.setOwner(getString(REV_UID));
data.setPermissions(getString(REV_PERMS));
data.setSelinuxCtx(getString(REV_SELINUX_CTX));
data.setRevNumber(getString(REV_NUMBER));
data.setType(extractFileType());
return data;
}
/**
* Returns a ConfigFileData representation of this Form, similar to toData()
* however in addition it replicates the contents of the passed in revision
* rev to the ConfigFileData.. This is mainly used in the FileDetailAction
* where want the contents of a "non-displayable" file replicated on to the
* newer revision...
* @param rev the revision to replicate the content stream.
* @return the newly updated revision..
*/
public ConfigFileData toRevisedData(ConfigRevision rev) {
ConfigFileData data = toData();
boolean toBeBinary = (Boolean)get(REV_BINARY) == null ?
rev.isFile() && rev.getConfigContent().isBinary() :
isBinary();
if (!canDisplayContent(rev) || toBeBinary) {
data.processRevisedContentFrom(rev);
}
return data;
}
}