PageRenderTime 35ms CodeModel.GetById 8ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/hudson-core/src/main/java/hudson/util/FormValidation.java

http://github.com/hudson/hudson
Java | 503 lines | 235 code | 57 blank | 211 comment | 30 complexity | b83c66e632264d38e2698b0e9a55b776 MD5 | raw file
  1/*
  2 * The MIT License
  3 *
  4 * Copyright (c) 2004-2009, Sun Microsystems, Inc.
  5 *
  6 * Permission is hereby granted, free of charge, to any person obtaining a copy
  7 * of this software and associated documentation files (the "Software"), to deal
  8 * in the Software without restriction, including without limitation the rights
  9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10 * copies of the Software, and to permit persons to whom the Software is
 11 * furnished to do so, subject to the following conditions:
 12 *
 13 * The above copyright notice and this permission notice shall be included in
 14 * all copies or substantial portions of the Software.
 15 *
 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22 * THE SOFTWARE.
 23 */
 24package hudson.util;
 25
 26import hudson.EnvVars;
 27import hudson.Functions;
 28import hudson.ProxyConfiguration;
 29import hudson.Util;
 30import hudson.FilePath;
 31import static hudson.Util.fixEmpty;
 32import hudson.model.Hudson;
 33import org.kohsuke.stapler.HttpResponse;
 34import org.kohsuke.stapler.StaplerRequest;
 35import org.kohsuke.stapler.StaplerResponse;
 36import org.kohsuke.stapler.Stapler;
 37
 38import javax.servlet.ServletException;
 39import java.io.File;
 40import java.io.IOException;
 41import java.io.BufferedReader;
 42import java.io.InputStreamReader;
 43import java.net.URL;
 44import java.net.URLConnection;
 45import java.util.Locale;
 46
 47/**
 48 * Represents the result of the form field validation.
 49 *
 50 * <p>
 51 * Use one of the factory methods to create an instance, then return it from your <tt>doCheckXyz</tt>
 52 * method. (Via {@link HttpResponse}, the returned object will render the result into {@link StaplerResponse}.)
 53 * This way of designing form field validation allows you to reuse {@code doCheckXyz()} methods
 54 * programmatically as well (by using {@link #kind}.
 55 *
 56 * <p>
 57 * For typical validation needs, this class offers a number of {@code validateXXX(...)} methods, such as
 58 * {@link #validateExecutable(String)}. {@link FilePath} also has a number of {@code validateXXX(...)} methods
 59 * that you may be able to reuse.
 60 *
 61 * <p>
 62 * Also see {@link CVSSCM.DescriptorImpl#doCheckCvsRoot(String)} as an example.
 63 *
 64 * <p>
 65 * This class extends {@link IOException} so that it can be thrown from a method. This allows one to reuse
 66 * the checking logic as a part of the real computation, such as:
 67 *
 68 * <pre>
 69 * String getAntVersion(File antHome) throws FormValidation {
 70 *    if(!antHome.isDirectory())
 71 *        throw FormValidation.error(antHome+" doesn't look like a home directory");
 72 *    ...
 73 *    return IOUtils.toString(new File(antHome,"version"));
 74 * }
 75 *
 76 * ...
 77 *
 78 * public FormValidation doCheckAntVersion(@QueryParameter String f) {
 79 *     try {
 80 *         return ok(getAntVersion(new File(f)));
 81 *     } catch (FormValidation f) {
 82 *         return f;
 83 *     }
 84 * }
 85 *
 86 * ...
 87 *
 88 * public void {@linkplain hudson.tasks.Builder#perform(hudson.model.AbstractBuild, hudson.Launcher, hudson.model.BuildListener) perform}(...) {
 89 *     String version = getAntVersion(antHome);
 90 *     ...
 91 * }
 92 * </pre>
 93 *
 94 * @author Kohsuke Kawaguchi
 95 * @since 1.294
 96 */
 97public abstract class FormValidation extends IOException implements HttpResponse {
 98    /**
 99     * Indicates the kind of result.
100     */
101    public enum Kind {
102        /**
103         * Form field value was OK and no problem was detected.
104         */
105        OK,
106        /**
107         * Form field value contained something suspicious. For some limited use cases
108         * the value could be valid, but we suspect the user made a mistake.
109         */
110        WARNING,
111        /**
112         * Form field value contained a problem that should be corrected.
113         */
114        ERROR
115    }
116
117    /**
118     * Sends out a string error message that indicates an error.
119     *
120     * @param message
121     *      Human readable message to be sent. <tt>error(null)</tt>
122     *      can be used as <tt>ok()</tt>.
123     */
124    public static FormValidation error(String message) {
125        return errorWithMarkup(message==null?null: Util.escape(message));
126    }
127
128    public static FormValidation warning(String message) {
129        return warningWithMarkup(message==null?null:Util.escape(message));
130    }
131
132    public static FormValidation ok(String message) {
133        return okWithMarkup(message==null?null:Util.escape(message));
134    }
135
136    /**
137     * Singleton instance that represents "OK".
138     */
139    private static final FormValidation OK = respond(Kind.OK,"<div/>");
140
141    public static FormValidation ok() {
142        return OK;
143    }
144
145    /**
146     * Sends out a string error message that indicates an error,
147     * by formatting it with {@link String#format(String, Object[])}
148     */
149    public static FormValidation error(String format, Object... args) {
150        return error(String.format(format,args));
151    }
152
153    public static FormValidation warning(String format, Object... args) {
154        return warning(String.format(format,args));
155    }
156
157    public static FormValidation ok(String format, Object... args) {
158        return ok(String.format(format,args));
159    }
160
161    /**
162     * Sends out a string error message, with optional "show details" link that expands to the full stack trace.
163     *
164     * <p>
165     * Use this with caution, so that anonymous users do not gain too much insights into the state of the system,
166     * as error stack trace often reveals a lot of information. Consider if a check operation needs to be exposed
167     * to everyone or just those who have higher access to job/hudson/etc.
168     */
169    public static FormValidation error(Throwable e, String message) {
170        return _error(Kind.ERROR, e, message);
171    }
172
173    public static FormValidation warning(Throwable e, String message) {
174        return _error(Kind.WARNING, e, message);
175    }
176
177    private static FormValidation _error(Kind kind, Throwable e, String message) {
178        if (e==null)    return _errorWithMarkup(Util.escape(message),kind);
179
180        return _errorWithMarkup(Util.escape(message)+
181            " <a href='#' class='showDetails'>"
182            + Messages.FormValidation_Error_Details()
183            + "</a><pre style='display:none'>"
184            + Functions.printThrowable(e) +
185            "</pre>",kind
186        );
187    }
188
189    public static FormValidation error(Throwable e, String format, Object... args) {
190        return error(e,String.format(format,args));
191    }
192
193    public static FormValidation warning(Throwable e, String format, Object... args) {
194        return warning(e,String.format(format,args));
195    }
196
197
198
199    /**
200     * Sends out an HTML fragment that indicates an error.
201     *
202     * <p>
203     * This method must be used with care to avoid cross-site scripting
204     * attack.
205     *
206     * @param message
207     *      Human readable message to be sent. <tt>error(null)</tt>
208     *      can be used as <tt>ok()</tt>.
209     */
210    public static FormValidation errorWithMarkup(String message) {
211        return _errorWithMarkup(message,Kind.ERROR);
212    }
213
214    public static FormValidation warningWithMarkup(String message) {
215        return _errorWithMarkup(message,Kind.WARNING);
216    }
217
218    public static FormValidation okWithMarkup(String message) {
219        return _errorWithMarkup(message,Kind.OK);
220    }
221
222    private static FormValidation _errorWithMarkup(final String message, final Kind kind) {
223        if(message==null)
224            return ok();
225        return new FormValidation(kind, message) {
226            public String renderHtml() {
227                // 1x16 spacer needed for IE since it doesn't support min-height
228                return "<div class="+ kind.name().toLowerCase(Locale.ENGLISH) +"><img src='"+
229                        Stapler.getCurrentRequest().getContextPath()+ Hudson.RESOURCE_PATH+"/images/none.gif' height=16 width=1>"+
230                        message+"</div>";
231            }
232        };
233    }
234
235    /**
236     * Sends out an arbitrary HTML fragment as the output.
237     */
238    public static FormValidation respond(Kind kind, final String html) {
239        return new FormValidation(kind) {
240            public String renderHtml() {
241                return html;
242            }
243        };
244    }
245
246    /**
247     * Performs an application-specific validation on the given file.
248     *
249     * <p>
250     * This is used as a piece in a bigger validation effort.
251     */
252    public static abstract class FileValidator {
253        public abstract FormValidation validate(File f);
254
255        /**
256         * Singleton instance that does no check.
257         */
258        public static final FileValidator NOOP = new FileValidator() {
259            public FormValidation validate(File f) {
260                return ok();
261            }
262        };
263    }
264
265    /**
266     * Makes sure that the given string points to an executable file.
267     */
268    public static FormValidation validateExecutable(String exe) {
269        return validateExecutable(exe, FileValidator.NOOP);
270    }
271
272    /**
273     * Makes sure that the given string points to an executable file.
274     *
275     * @param exeValidator
276     *      If the validation process discovers a valid executable program on the given path,
277     *      the specified {@link FileValidator} can perform additional checks (such as making sure
278     *      that it has the right version, etc.)
279     */
280    public static FormValidation validateExecutable(String exe, FileValidator exeValidator) {
281        // insufficient permission to perform validation?
282        if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) return ok();
283
284        exe = fixEmpty(exe);
285        if(exe==null)
286            return ok();
287
288        if(exe.indexOf(File.separatorChar)>=0) {
289            // this is full path
290            File f = new File(exe);
291            if(f.exists())  return exeValidator.validate(f);
292
293            File fexe = new File(exe+".exe");
294            if(fexe.exists())   return exeValidator.validate(fexe);
295
296            return error("There's no such file: "+exe);
297        }
298
299        // look in PATH
300        String path = EnvVars.masterEnvVars.get("PATH");
301        String tokenizedPath = "";
302        String delimiter = null;
303        if(path!=null) {
304            for (String _dir : Util.tokenize(path.replace("\\", "\\\\"),File.pathSeparator)) {
305                if (delimiter == null) {
306                  delimiter = ", ";
307                }
308                else {
309                  tokenizedPath += delimiter;
310                }
311
312                tokenizedPath += _dir.replace('\\', '/');
313
314                File dir = new File(_dir);
315
316                File f = new File(dir,exe);
317                if(f.exists())  return exeValidator.validate(f);
318
319                File fexe = new File(dir,exe+".exe");
320                if(fexe.exists())   return exeValidator.validate(fexe);
321            }
322
323            tokenizedPath += ".";
324        } else {
325            tokenizedPath = "unavailable.";
326        }
327
328        // didn't find it
329        return error("There's no such executable "+exe+" in PATH: "+tokenizedPath);
330    }
331
332    /**
333     * Makes sure that the given string is a non-negative integer.
334     */
335    public static FormValidation validateNonNegativeInteger(String value) {
336        try {
337            if(Integer.parseInt(value)<0)
338                return error(hudson.model.Messages.Hudson_NotANonNegativeNumber());
339            return ok();
340        } catch (NumberFormatException e) {
341            return error(hudson.model.Messages.Hudson_NotANumber());
342        }
343    }
344
345    /**
346     * Makes sure that the given string is a positive integer.
347     */
348    public static FormValidation validatePositiveInteger(String value) {
349        try {
350            if(Integer.parseInt(value)<=0)
351                return error(hudson.model.Messages.Hudson_NotAPositiveNumber());
352            return ok();
353        } catch (NumberFormatException e) {
354            return error(hudson.model.Messages.Hudson_NotANumber());
355        }
356    }
357
358    /**
359     * Makes sure that the given string is not null or empty.
360     */
361    public static FormValidation validateRequired(String value) {
362        if (Util.fixEmptyAndTrim(value) == null)
363            return error(Messages.FormValidation_ValidateRequired());
364        return ok();
365    }
366
367    /**
368     * Makes sure that the given string is a base64 encoded text.
369     *
370     * @param allowWhitespace
371     *      if you allow whitespace (CR,LF,etc) in base64 encoding
372     * @param allowEmpty
373     *      Is empty string allowed?
374     * @param errorMessage
375     *      Error message.
376     * @since 1.305
377     */
378    public static FormValidation validateBase64(String value, boolean allowWhitespace, boolean allowEmpty, String errorMessage) {
379        try {
380            String v = value;
381            if(!allowWhitespace) {
382                if(v.indexOf(' ')>=0 || v.indexOf('\n')>=0)
383                    return error(errorMessage);
384            }
385            v=v.trim();
386            if(!allowEmpty && v.length()==0)
387                return error(errorMessage);
388
389            com.trilead.ssh2.crypto.Base64.decode(v.toCharArray());
390            return ok();
391        } catch (IOException e) {
392            return error(errorMessage);
393        }
394    }
395
396    /**
397     * Convenient base class for checking the validity of URLs.
398     *
399     * <p>
400     * This allows the check method to call various utility methods in a concise syntax.
401     */
402    public static abstract class URLCheck {
403        /**
404         * Opens the given URL and reads text content from it.
405         * This method honors Content-type header.
406         */
407        protected BufferedReader open(URL url) throws IOException {
408            // use HTTP content type to find out the charset.
409            URLConnection con = ProxyConfiguration.open(url);
410            if (con == null) { // XXX is this even permitted by URL.openConnection?
411                throw new IOException(url.toExternalForm());
412            }
413            return new BufferedReader(
414                new InputStreamReader(con.getInputStream(),getCharset(con)));
415        }
416
417        /**
418         * Finds the string literal from the given reader.
419         * @return
420         *      true if found, false otherwise.
421         */
422        protected boolean findText(BufferedReader in, String literal) throws IOException {
423            String line;
424            while((line=in.readLine())!=null)
425                if(line.indexOf(literal)!=-1)
426                    return true;
427            return false;
428        }
429
430        /**
431         * Calls the {@link FormValidation#error(String)} method with a reasonable error message.
432         * Use this method when the {@link #open(URL)} or {@link #findText(BufferedReader, String)} fails.
433         *
434         * @param url
435         *      Pass in the URL that was connected. Used for error diagnosis.
436         */
437        protected FormValidation handleIOException(String url, IOException e) throws IOException, ServletException {
438            // any invalid URL comes here
439            if(e.getMessage().equals(url))
440                // Sun JRE (and probably others too) often return just the URL in the error.
441                return error("Unable to connect "+url);
442            else
443                return error(e.getMessage());
444        }
445
446        /**
447         * Figures out the charset from the content-type header.
448         */
449        private String getCharset(URLConnection con) {
450            for( String t : con.getContentType().split(";") ) {
451                t = t.trim().toLowerCase(Locale.ENGLISH);
452                if(t.startsWith("charset="))
453                    return t.substring(8);
454            }
455            // couldn't find it. HTML spec says default is US-ASCII,
456            // but UTF-8 is a better choice since
457            // (1) it's compatible with US-ASCII
458            // (2) a well-written web applications tend to use UTF-8
459            return "UTF-8";
460        }
461
462        /**
463         * Implement the actual form validation logic, by using other convenience methosd defined in this class.
464         * If you are not using any of those, you don't need to extend from this class.
465         */
466        protected abstract FormValidation check() throws IOException, ServletException;
467    }
468
469
470    //TODO: review and check whether we can do it private
471    public final Kind kind;
472
473    public Kind getKind() {
474        return kind;
475    }
476
477    /**
478     * Instances should be created via one of the factory methods above.
479     * @param kind
480     */
481    private FormValidation(Kind kind) {
482        this.kind = kind;
483    }
484
485    private FormValidation(Kind kind, String message) {
486        super(message);
487        this.kind = kind;
488    }
489
490    public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
491        respond(rsp, renderHtml());
492    }
493
494    public abstract String renderHtml();
495
496    /**
497     * Sends out an arbitrary HTML fragment as the output.
498     */
499    protected void respond(StaplerResponse rsp, String html) throws IOException, ServletException {
500        rsp.setContentType("text/html;charset=UTF-8");
501        rsp.getWriter().print(html);
502    }
503}