hudson.util.FormValidation.java Source code

Java tutorial

Introduction

Here is the source code for hudson.util.FormValidation.java

Source

/*******************************************************************************
 *
 * Copyright (c) 2004-2009, Oracle Corporation
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *
 *
 *
 *
 *******************************************************************************/

package hudson.util;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.Functions;
import hudson.ProxyConfiguration;
import hudson.Util;
import static hudson.Util.fixEmpty;
import hudson.model.Hudson;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import javax.servlet.ServletException;
import org.apache.commons.codec.binary.Base64;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

/**
 * Represents the result of the form field validation.
 *
 * <p> Use one of the factory methods to create an instance, then return it from
 * your <tt>doCheckXyz</tt> method. (Via {@link HttpResponse}, the returned
 * object will render the result into {@link StaplerResponse}.) This way of
 * designing form field validation allows you to reuse {@code doCheckXyz()}
 * methods programmatically as well (by using {@link #kind}.
 *
 * <p> For typical validation needs, this class offers a number of
 * {@code validateXXX(...)} methods, such as
 * {@link #validateExecutable(String)}. {@link FilePath} also has a number of
 * {@code validateXXX(...)} methods that you may be able to reuse.
 *
 * <p> Also see {@link CVSSCM.DescriptorImpl#doCheckCvsRoot(String)} as an
 * example.
 *
 * <p> This class extends {@link IOException} so that it can be thrown from a
 * method. This allows one to reuse the checking logic as a part of the real
 * computation, such as:
 *
 * <pre>
 * String getAntVersion(File antHome) throws FormValidation {
 *    if(!antHome.isDirectory())
 *        throw FormValidation.error(antHome+" doesn't look like a home directory");
 *    ...
 *    return IOUtils.toString(new File(antHome,"version"));
 * }
 *
 * ...
 *
 * public FormValidation doCheckAntVersion(
 *
 * @QueryParameter String f) { try { return ok(getAntVersion(new File(f))); }
 * catch (FormValidation f) { return f; } }
 *
 * ...
 *
 * public void
 * {@linkplain hudson.tasks.Builder#perform(hudson.model.AbstractBuild, hudson.Launcher, hudson.model.BuildListener) perform}(...)
 * { String version = getAntVersion(antHome); ... }
 * </pre>
 *
 * @author Kohsuke Kawaguchi
 * @since 1.294
 */
public abstract class FormValidation extends IOException implements HttpResponse {

    /**
     * Indicates the kind of result.
     */
    public enum Kind {

        /**
         * Form field value was OK and no problem was detected.
         */
        OK,
        /**
         * Form field value contained something suspicious. For some limited use
         * cases the value could be valid, but we suspect the user made a
         * mistake.
         */
        WARNING,
        /**
         * Form field value contained a problem that should be corrected.
         */
        ERROR
    }

    /**
     * Sends out a string error message that indicates an error.
     *
     * @param message Human readable message to be sent. <tt>error(null)</tt>
     * can be used as <tt>ok()</tt>.
     */
    public static FormValidation error(String message) {
        return errorWithMarkup(message == null ? null : Util.escape(message));
    }

    public static FormValidation warning(String message) {
        return warningWithMarkup(message == null ? null : Util.escape(message));
    }

    public static FormValidation ok(String message) {
        return okWithMarkup(message == null ? null : Util.escape(message));
    }

    /**
     * Singleton instance that represents "OK".
     */
    private static final FormValidation OK = respond(Kind.OK, "<div/>");

    public static FormValidation ok() {
        return OK;
    }

    /**
     * Sends out a string error message that indicates an error, by formatting
     * it with {@link String#format(String, Object[])}
     */
    public static FormValidation error(String format, Object... args) {
        return error(String.format(format, args));
    }

    public static FormValidation warning(String format, Object... args) {
        return warning(String.format(format, args));
    }

    public static FormValidation ok(String format, Object... args) {
        return ok(String.format(format, args));
    }

    /**
     * Sends out a string error message, with optional "show details" link that
     * expands to the full stack trace.
     *
     * <p> Use this with caution, so that anonymous users do not gain too much
     * insights into the state of the system, as error stack trace often reveals
     * a lot of information. Consider if a check operation needs to be exposed
     * to everyone or just those who have higher access to job/hudson/etc.
     */
    public static FormValidation error(Throwable e, String message) {
        return _error(Kind.ERROR, e, message);
    }

    public static FormValidation warning(Throwable e, String message) {
        return _error(Kind.WARNING, e, message);
    }

    private static FormValidation _error(Kind kind, Throwable e, String message) {
        if (e == null) {
            return _errorWithMarkup(Util.escape(message), kind);
        }

        return _errorWithMarkup(
                Util.escape(message) + " <a href='#' class='showDetails'>" + Messages.FormValidation_Error_Details()
                        + "</a><pre style='display:none'>" + Functions.printThrowable(e) + "</pre>",
                kind);
    }

    public static FormValidation error(Throwable e, String format, Object... args) {
        return error(e, String.format(format, args));
    }

    public static FormValidation warning(Throwable e, String format, Object... args) {
        return warning(e, String.format(format, args));
    }

    /**
     * Sends out an HTML fragment that indicates an error.
     *
     * <p> This method must be used with care to avoid cross-site scripting
     * attack.
     *
     * @param message Human readable message to be sent. <tt>error(null)</tt>
     * can be used as <tt>ok()</tt>.
     */
    public static FormValidation errorWithMarkup(String message) {
        return _errorWithMarkup(message, Kind.ERROR);
    }

    public static FormValidation warningWithMarkup(String message) {
        return _errorWithMarkup(message, Kind.WARNING);
    }

    public static FormValidation okWithMarkup(String message) {
        return _errorWithMarkup(message, Kind.OK);
    }

    private static FormValidation _errorWithMarkup(final String message, final Kind kind) {
        if (message == null) {
            return ok();
        }
        return new FormValidation(kind, message) {
            public String renderHtml() {
                // 1x16 spacer needed for IE since it doesn't support min-height
                return "<div class=" + kind.name().toLowerCase(Locale.ENGLISH) + "><img src='"
                        + Functions.getRequestRootPath() + Hudson.RESOURCE_PATH
                        + "/images/none.gif' height=16 width=1>" + message + "</div>";
            }
        };
    }

    /**
     * Sends out an arbitrary HTML fragment as the output.
     */
    public static FormValidation respond(Kind kind, final String html) {
        return new FormValidation(kind) {
            public String renderHtml() {
                return html;
            }
        };
    }

    /**
     * Performs an application-specific validation on the given file.
     *
     * <p> This is used as a piece in a bigger validation effort.
     */
    public static abstract class FileValidator {

        public abstract FormValidation validate(File f);

        /**
         * Singleton instance that does no check.
         */
        public static final FileValidator NOOP = new FileValidator() {
            public FormValidation validate(File f) {
                return ok();
            }
        };
    }

    /**
     * Makes sure that the given string points to an executable file.
     */
    public static FormValidation validateExecutable(String exe) {
        return validateExecutable(exe, FileValidator.NOOP);
    }

    /**
     * Makes sure that the given string points to an executable file.
     *
     * @param exeValidator If the validation process discovers a valid
     * executable program on the given path, the specified {@link FileValidator}
     * can perform additional checks (such as making sure that it has the right
     * version, etc.)
     */
    public static FormValidation validateExecutable(String exe, FileValidator exeValidator) {
        // insufficient permission to perform validation?
        if (!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) {
            return ok();
        }

        exe = fixEmpty(exe);
        if (exe == null) {
            return ok();
        }

        if (exe.indexOf(File.separatorChar) >= 0) {
            // this is full path
            File f = new File(exe);
            if (f.exists()) {
                return exeValidator.validate(f);
            }

            File fexe = new File(exe + ".exe");
            if (fexe.exists()) {
                return exeValidator.validate(fexe);
            }

            return error("There's no such file: " + exe);
        }

        // look in PATH
        String path = EnvVars.masterEnvVars.get("PATH");
        String tokenizedPath = "";
        String delimiter = null;
        if (path != null) {
            for (String _dir : Util.tokenize(path.replace("\\", "\\\\"), File.pathSeparator)) {
                if (delimiter == null) {
                    delimiter = ", ";
                } else {
                    tokenizedPath += delimiter;
                }

                tokenizedPath += _dir.replace('\\', '/');

                File dir = new File(_dir);

                File f = new File(dir, exe);
                if (f.exists()) {
                    return exeValidator.validate(f);
                }

                File fexe = new File(dir, exe + ".exe");
                if (fexe.exists()) {
                    return exeValidator.validate(fexe);
                }
            }

            tokenizedPath += ".";
        } else {
            tokenizedPath = "unavailable.";
        }

        // didn't find it
        return error("There's no such executable " + exe + " in PATH: " + tokenizedPath);
    }

    /**
     * Makes sure that the given string is a non-negative integer.
     */
    public static FormValidation validateNonNegativeInteger(String value) {
        try {
            if (Integer.parseInt(value) < 0) {
                return error(hudson.model.Messages.Hudson_NotANonNegativeNumber());
            }
            return ok();
        } catch (NumberFormatException e) {
            return error(hudson.model.Messages.Hudson_NotANumber());
        }
    }

    /**
     * Makes sure that the given string is a positive integer.
     */
    public static FormValidation validatePositiveInteger(String value) {
        try {
            if (Integer.parseInt(value) <= 0) {
                return error(hudson.model.Messages.Hudson_NotAPositiveNumber());
            }
            return ok();
        } catch (NumberFormatException e) {
            return error(hudson.model.Messages.Hudson_NotANumber());
        }
    }

    /**
     * Makes sure that the given string is not null or empty.
     */
    public static FormValidation validateRequired(String value) {
        if (Util.fixEmptyAndTrim(value) == null) {
            return error(Messages.FormValidation_ValidateRequired());
        }
        return ok();
    }

    /**
     * Makes sure that the given string is a base64 encoded text.
     *
     * @param allowWhitespace if you allow whitespace (CR,LF,etc) in base64
     * encoding
     * @param allowEmpty Is empty string allowed?
     * @param errorMessage Error message.
     * @since 1.305
     */
    public static FormValidation validateBase64(String value, boolean allowWhitespace, boolean allowEmpty,
            String errorMessage) {
        String v = value;
        if (!allowWhitespace) {
            if (v.indexOf(' ') >= 0 || v.indexOf('\n') >= 0) {
                return error(errorMessage);
            }
        }
        v = v.trim();
        if (!allowEmpty && v.length() == 0) {
            return error(errorMessage);
        }

        Base64.decodeBase64(v);
        return ok();
    }

    /**
     * Convenient base class for checking the validity of URLs.
     *
     * <p> This allows the check method to call various utility methods in a
     * concise syntax.
     */
    public static abstract class URLCheck {

        /**
         * Opens the given URL and reads text content from it. This method
         * honors Content-type header.
         */
        protected BufferedReader open(URL url) throws IOException {
            // use HTTP content type to find out the charset.
            URLConnection con = ProxyConfiguration.open(url);
            if (con == null) { // XXX is this even permitted by URL.openConnection?
                throw new IOException(url.toExternalForm());
            }
            return new BufferedReader(new InputStreamReader(con.getInputStream(), getCharset(con)));
        }

        /**
         * Finds the string literal from the given reader.
         *
         * @return true if found, false otherwise.
         */
        protected boolean findText(BufferedReader in, String literal) throws IOException {
            String line;
            while ((line = in.readLine()) != null) {
                if (line.indexOf(literal) != -1) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Calls the {@link FormValidation#error(String)} method with a
         * reasonable error message. Use this method when the {@link #open(URL)}
         * or {@link #findText(BufferedReader, String)} fails.
         *
         * @param url Pass in the URL that was connected. Used for error
         * diagnosis.
         */
        protected FormValidation handleIOException(String url, IOException e) throws IOException, ServletException {
            // any invalid URL comes here
            if (e.getMessage().equals(url)) // Sun JRE (and probably others too) often return just the URL in the error.
            {
                return error("Unable to connect " + url);
            } else {
                return error(e.getMessage());
            }
        }

        /**
         * Figures out the charset from the content-type header.
         */
        private String getCharset(URLConnection con) {
            for (String t : con.getContentType().split(";")) {
                t = t.trim().toLowerCase(Locale.ENGLISH);
                if (t.startsWith("charset=")) {
                    return t.substring(8);
                }
            }
            // couldn't find it. HTML spec says default is US-ASCII,
            // but UTF-8 is a better choice since
            // (1) it's compatible with US-ASCII
            // (2) a well-written web applications tend to use UTF-8
            return "UTF-8";
        }

        /**
         * Implement the actual form validation logic, by using other
         * convenience methosd defined in this class. If you are not using any
         * of those, you don't need to extend from this class.
         */
        protected abstract FormValidation check() throws IOException, ServletException;
    }

    //TODO: review and check whether we can do it private
    public final Kind kind;

    public Kind getKind() {
        return kind;
    }

    /**
     * Instances should be created via one of the factory methods above.
     *
     * @param kind
     */
    private FormValidation(Kind kind) {
        this.kind = kind;
    }

    private FormValidation(Kind kind, String message) {
        super(message);
        this.kind = kind;
    }

    public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node)
            throws IOException, ServletException {
        respond(rsp, renderHtml());
    }

    public abstract String renderHtml();

    /**
     * Sends out an arbitrary HTML fragment as the output.
     */
    protected void respond(StaplerResponse rsp, String html) throws IOException, ServletException {
        rsp.setContentType("text/html;charset=UTF-8");
        rsp.getWriter().print(html);
    }
}