/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jene Jasper, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.util; import static hudson.Util.fixEmpty; import hudson.EnvVars; import hudson.FilePath; import hudson.ProxyConfiguration; import hudson.Util; import hudson.model.AbstractProject; import jenkins.model.Jenkins; import hudson.model.Item; import hudson.security.Permission; import hudson.security.AccessControlled; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Locale; import javax.servlet.ServletException; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.acegisecurity.AccessDeniedException; import org.kohsuke.stapler.Stapler; /** * Base class that provides the framework for doing on-the-fly form field validation. * * <p> * The {@link #check()} method is to be implemented by derived classes to perform * the validation. See hudson-behavior.js 'validated' CSS class and 'checkUrl' attribute. * * @author Kohsuke Kawaguchi * @deprecated as of 1.294 * Use {@link FormValidation} as a return value in your check method. */ @Deprecated public abstract class FormFieldValidator { public static final Permission CHECK = Jenkins.ADMINISTER; protected final StaplerRequest request; protected final StaplerResponse response; /** * Permission to check, or null if this check doesn't require any permission. */ protected final Permission permission; /** * The object to which the permission is checked against. * If {@link #permission} is non-null, must be non-null. */ protected final AccessControlled subject; /** * @param adminOnly * Pass true to only let admin users to run the check. This is necessary * for security reason, so that unauthenticated user cannot obtain sensitive * information or run a process that may have side-effect. */ protected FormFieldValidator(StaplerRequest request, StaplerResponse response, boolean adminOnly) { this(request, response, adminOnly? Jenkins.getInstance():null, adminOnly?CHECK:null); } /** * @deprecated * Use {@link #FormFieldValidator(Permission)} and remove {@link StaplerRequest} and {@link StaplerResponse} * from your "doCheck..." method parameter */ @Deprecated protected FormFieldValidator(StaplerRequest request, StaplerResponse response, Permission permission) { this(request,response, Jenkins.getInstance(),permission); } /** * @param permission * Permission needed to perform this validation, or null if no permission is necessary. */ protected FormFieldValidator(Permission permission) { this(Stapler.getCurrentRequest(),Stapler.getCurrentResponse(),permission); } /** * @deprecated * Use {@link #FormFieldValidator(AccessControlled,Permission)} and remove {@link StaplerRequest} and {@link StaplerResponse} * from your "doCheck..." method parameter */ @Deprecated protected FormFieldValidator(StaplerRequest request, StaplerResponse response, AccessControlled subject, Permission permission) { this.request = request; this.response = response; this.subject = subject; this.permission = permission; } protected FormFieldValidator(AccessControlled subject, Permission permission) { this(Stapler.getCurrentRequest(),Stapler.getCurrentResponse(),subject,permission); } /** * Runs the validation code. */ public final void process() throws IOException, ServletException { if(permission!=null) try { if(subject==null) throw new AccessDeniedException("No subject"); subject.checkPermission(permission); } catch (AccessDeniedException e) { // if the user has hudson-wisde admin permission, all checks are allowed // this is to protect Hudson administrator from broken ACL/SecurityRealm implementation/configuration. if(!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) throw e; } check(); } protected abstract void check() throws IOException, ServletException; /** * Gets the parameter as a file. */ protected final File getFileParameter(String paramName) { return new File(Util.fixNull(request.getParameter(paramName))); } /** * Sends out an HTML fragment that indicates a success. */ public void ok() throws IOException, ServletException { respond("<div/>"); } /** * Sends out an arbitrary HTML fragment. */ public void respond(String html) throws IOException, ServletException { response.setContentType("text/html"); response.getWriter().print(html); } /** * 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 void error(String message) throws IOException, ServletException { errorWithMarkup(message==null?null:Util.escape(message)); } public void warning(String message) throws IOException, ServletException { warningWithMarkup(message==null?null:Util.escape(message)); } public void ok(String message) throws IOException, ServletException { okWithMarkup(message==null?null:Util.escape(message)); } /** * Sends out a string error message that indicates an error, * by formatting it with {@link String#format(String, Object[])} */ public void error(String format, Object... args) throws IOException, ServletException { error(String.format(format,args)); } public void warning(String format, Object... args) throws IOException, ServletException { warning(String.format(format,args)); } public void ok(String format, Object... args) throws IOException, ServletException { ok(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 void errorWithMarkup(String message) throws IOException, ServletException { _errorWithMarkup(message,"error"); } public void warningWithMarkup(String message) throws IOException, ServletException { _errorWithMarkup(message,"warning"); } public void okWithMarkup(String message) throws IOException, ServletException { _errorWithMarkup(message,"ok"); } private void _errorWithMarkup(String message, String cssClass) throws IOException, ServletException { if(message==null) { ok(); } else { response.setContentType("text/html;charset=UTF-8"); // 1x16 spacer needed for IE since it doesn't support min-height response.getWriter().print("<div class="+ cssClass +"><img src='"+ request.getContextPath()+ Jenkins.RESOURCE_PATH+"/images/none.gif' height=16 width=1>"+ message+"</div>"); } } /** * Convenient base class for checking the validity of URLs * * @deprecated as of 1.294 * Use {@link FormValidation.URLCheck} */ @Deprecated public static abstract class URLCheck extends FormFieldValidator { public URLCheck(StaplerRequest request, StaplerResponse response) { // can be used to check the existence of any file in file system // or other HTTP URLs inside firewall, so limit this to the admin. super(request, response, true); } /** * 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) { // TODO 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 #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 void 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. error("Unable to connect "+url); else 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"; } } /** * Checks if the given value is an URL to some Hudson's top page. * @since 1.192 */ public static class HudsonURL extends URLCheck { public HudsonURL(StaplerRequest request, StaplerResponse response) { super(request, response); } protected void check() throws IOException, ServletException { String value = fixEmpty(request.getParameter("value")); if(value==null) {// nothing entered yet ok(); return; } if(!value.endsWith("/")) value+='/'; try { URL url = new URL(value); HttpURLConnection con = (HttpURLConnection)url.openConnection(); con.connect(); if(con.getResponseCode()!=200 || con.getHeaderField("X-Hudson")==null) { error(value+" is not Hudson ("+con.getResponseMessage()+")"); return; } ok(); } catch (IOException e) { handleIOException(value,e); } } } /** * Checks the file mask (specified in the 'value' query parameter) against * the current workspace. * @since 1.90. * @deprecated as of 1.294. Use {@link FilePath#validateFileMask(String, boolean, boolean)} */ @Deprecated public static class WorkspaceFileMask extends FormFieldValidator { private final boolean errorIfNotExist; public WorkspaceFileMask(StaplerRequest request, StaplerResponse response) { this(request, response, true); } public WorkspaceFileMask(StaplerRequest request, StaplerResponse response, boolean errorIfNotExist) { // Require CONFIGURE permission on the job super(request, response, request.findAncestorObject(AbstractProject.class), Item.CONFIGURE); this.errorIfNotExist = errorIfNotExist; } protected void check() throws IOException, ServletException { String value = fixEmpty(request.getParameter("value")); AbstractProject<?,?> p = (AbstractProject<?,?>)subject; if(value==null || p==null) { ok(); // none entered yet, or something is seriously wrong return; } try { FilePath ws = getBaseDirectory(p); if(ws==null || !ws.exists()) {// no workspace. can't check ok(); return; } String msg = ws.validateAntFileMask(value, FilePath.VALIDATE_ANT_FILE_MASK_BOUND); if(errorIfNotExist) error(msg); else warning(msg); } catch (InterruptedException e) { ok(Messages.FormFieldValidator_did_not_manage_to_validate_may_be_too_sl(value)); } } /** * The base directory from which the path name is resolved. */ protected FilePath getBaseDirectory(AbstractProject<?,?> p) { return p.getSomeWorkspace(); } } /** * Checks a valid directory name (specified in the 'value' query parameter) against * the current workspace. * @since 1.116 * @deprecated as of 1.294. Use {@link FilePath#validateRelativeDirectory(String, boolean)} * (see javadoc plugin for the example) */ @Deprecated public static class WorkspaceDirectory extends WorkspaceFilePath { public WorkspaceDirectory(StaplerRequest request, StaplerResponse response, boolean errorIfNotExist) { super(request, response, errorIfNotExist, false); } public WorkspaceDirectory(StaplerRequest request, StaplerResponse response) { this(request, response, true); } } /** * Checks a valid file name or directory (specified in the 'value' query parameter) against * the current workspace. * @since 1.160 * @deprecated as of 1.294. Use {@link FilePath#validateRelativePath(String, boolean, boolean)} */ @Deprecated public static class WorkspaceFilePath extends FormFieldValidator { private final boolean errorIfNotExist; private final boolean expectingFile; public WorkspaceFilePath(StaplerRequest request, StaplerResponse response, boolean errorIfNotExist, boolean expectingFile) { // Require CONFIGURE permission on this job super(request, response, request.findAncestorObject(AbstractProject.class), Item.CONFIGURE); this.errorIfNotExist = errorIfNotExist; this.expectingFile = expectingFile; } protected void check() throws IOException, ServletException { String value = fixEmpty(request.getParameter("value")); AbstractProject<?,?> p = (AbstractProject<?,?>)subject; if(value==null || p==null) { ok(); // none entered yet, or something is seriously wrong return; } if(value.contains("*")) { // a common mistake is to use wildcard error("Wildcard is not allowed here"); return; } try { FilePath ws = getBaseDirectory(p); if(ws==null) {// can't check ok(); return; } if(!ws.exists()) {// no workspace. can't check ok(); return; } if(ws.child(value).exists()) { if (expectingFile) { if(!ws.child(value).isDirectory()) ok(); else error(value+" is not a file"); } else { if(ws.child(value).isDirectory()) ok(); else error(value+" is not a directory"); } } else { String msg = "No such "+(expectingFile?"file":"directory")+": " + value; if(errorIfNotExist) error(msg); else warning(msg); } } catch (InterruptedException e) { ok(); // coundn't check } } /** * The base directory from which the path name is resolved. */ protected FilePath getBaseDirectory(AbstractProject<?,?> p) { return p.getSomeWorkspace(); } } /** * Checks a valid executable binary (specified in the 'value' query parameter). * It has to be either given as a full path to the executable, or else * it will be searched in PATH. * * <p> * This file also handles ".exe" omission in Windows --- I thought Windows * has actually more generic mechanism for the executable extension omission, * so perhaps this needs to be extended to handle that correctly. More info * needed. * * @since 1.124 * @deprecated as of 1.294. Use {@link FormValidation#validateExecutable(String)} */ @Deprecated public static class Executable extends FormFieldValidator { public Executable(StaplerRequest request, StaplerResponse response) { // Require admin permission super(request, response, true); } protected void check() throws IOException, ServletException { String exe = fixEmpty(request.getParameter("value")); if(exe==null) { ok(); // nothing entered yet return; } if(exe.indexOf(File.separatorChar)>=0) { // this is full path File f = new File(exe); if(f.exists()) { checkExecutable(f); return; } File fexe = new File(exe+".exe"); if(fexe.exists()) { checkExecutable(fexe); return; } error("There's no such file: "+exe); } else { // 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()) { checkExecutable(f); return; } File fexe = new File(dir,exe+".exe"); if(fexe.exists()) { checkExecutable(fexe); return; } } tokenizedPath += "."; } else { tokenizedPath = "unavailable."; } // didn't find it error("There's no such executable "+exe+" in PATH: "+tokenizedPath); } } /** * Provides an opportunity for derived classes to do additional checks on the executable. */ protected void checkExecutable(File exe) throws IOException, ServletException { ok(); } } /** * Verifies that the 'value' parameter is correct base64 encoded text. * * @since 1.257 * @deprecated as of 1.305 * Use {@link FormValidation#validateBase64(String, boolean, boolean, String)} instead. */ @Deprecated public static class Base64 extends FormFieldValidator { private final boolean allowWhitespace; private final boolean allowEmpty; private final String errorMessage; public Base64(StaplerRequest request, StaplerResponse response, boolean allowWhitespace, boolean allowEmpty, String errorMessage) { super(request, response, false); this.allowWhitespace = allowWhitespace; this.allowEmpty = allowEmpty; this.errorMessage = errorMessage; } protected void check() throws IOException, ServletException { try { String v = request.getParameter("value"); if(!allowWhitespace) { if(v.indexOf(' ')>=0 || v.indexOf('\n')>=0) { fail(); return; } } v=v.trim(); if(!allowEmpty && v.length()==0) { fail(); return; } com.trilead.ssh2.crypto.Base64.decode(v.toCharArray()); ok(); } catch (IOException e) { fail(); } } protected void fail() throws IOException, ServletException { error(errorMessage); } } /** * Verifies that the 'value' parameter is an integer >= 0. * * @since 1.282 * @deprecated as of 1.294 * Use {@link FormValidation#validateNonNegativeInteger(String)} */ @Deprecated public static class NonNegativeInteger extends FormFieldValidator { public NonNegativeInteger() { super(null); } protected void check() throws IOException, ServletException { try { String value = request.getParameter("value"); if(Integer.parseInt(value)<0) error(hudson.model.Messages.Hudson_NotAPositiveNumber()); else ok(); } catch (NumberFormatException e) { error(hudson.model.Messages.Hudson_NotANumber()); } } } }