diff --git a/pom.xml b/pom.xml index 17309b31ad..33b36731db 100644 --- a/pom.xml +++ b/pom.xml @@ -261,6 +261,17 @@ + + commons-validator + commons-validator + 1.6 + + + commons-digester + commons-digester + + + diff --git a/src/main/java/hudson/plugins/git/browser/AssemblaWeb.java b/src/main/java/hudson/plugins/git/browser/AssemblaWeb.java index a6627fae84..aa05edf63b 100644 --- a/src/main/java/hudson/plugins/git/browser/AssemblaWeb.java +++ b/src/main/java/hudson/plugins/git/browser/AssemblaWeb.java @@ -1,15 +1,19 @@ package hudson.plugins.git.browser; import hudson.Extension; +import hudson.Util; import hudson.model.Descriptor; +import hudson.model.Item; import hudson.plugins.git.GitChangeSet; import hudson.plugins.git.GitChangeSet.Path; +import hudson.plugins.git.Messages; import hudson.scm.EditType; import hudson.scm.RepositoryBrowser; import hudson.util.FormValidation; import hudson.util.FormValidation.URLCheck; -import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.apache.commons.validator.routines.UrlValidator; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.QueryParameter; @@ -18,6 +22,8 @@ import javax.annotation.Nonnull; import javax.servlet.ServletException; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; /** @@ -94,18 +100,21 @@ public AssemblaWeb newInstance(StaplerRequest req, @Nonnull JSONObject jsonObjec } @RequirePOST - public FormValidation doCheckUrl(@QueryParameter(fixEmpty = true) final String url) - throws IOException, ServletException { - if (url == null) // nothing entered yet + public FormValidation doCheckRepoUrl(@AncestorInPath Item project, @QueryParameter(fixEmpty = true) final String repoUrl) + throws IOException, ServletException, URISyntaxException { + + String cleanUrl = Util.fixEmptyAndTrim(repoUrl); + if (initialChecksAndReturnOk(project, cleanUrl)) { return FormValidation.ok(); } - // Connect to URL and check content only if we have admin permission - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) - return FormValidation.ok(); + // Connect to URL and check content only if we have permission + if (!checkURIFormatAndHostName(cleanUrl, "assembla")) { + return FormValidation.error(Messages.invalidUrl()); + } return new URLCheck() { protected FormValidation check() throws IOException, ServletException { - String v = url; + String v = cleanUrl; if (!v.endsWith("/")) { v += '/'; } @@ -114,7 +123,7 @@ protected FormValidation check() throws IOException, ServletException { if (findText(open(new URL(v)), "Assembla")) { return FormValidation.ok(); } else { - return FormValidation.error("This is a valid URL but it doesn't look like Assembla"); + return FormValidation.error("This is a valid URL but it does not look like Assembla"); } } catch (IOException e) { return handleIOException(v, e); @@ -122,5 +131,13 @@ protected FormValidation check() throws IOException, ServletException { } }.check(); } + + private boolean checkURIFormatAndHostName(String url, String hostNameFragment) throws URISyntaxException { + URI uri = new URI(url); + String[] schemes = {"http", "https"}; + UrlValidator urlValidator = new UrlValidator(schemes); + hostNameFragment = hostNameFragment + "."; + return urlValidator.isValid(uri.toString()) && uri.getHost().contains(hostNameFragment); + } } } diff --git a/src/main/java/hudson/plugins/git/browser/GitBlitRepositoryBrowser.java b/src/main/java/hudson/plugins/git/browser/GitBlitRepositoryBrowser.java index bfd8c4331b..5a4706a32f 100644 --- a/src/main/java/hudson/plugins/git/browser/GitBlitRepositoryBrowser.java +++ b/src/main/java/hudson/plugins/git/browser/GitBlitRepositoryBrowser.java @@ -1,15 +1,18 @@ package hudson.plugins.git.browser; import hudson.Extension; +import hudson.Util; import hudson.model.Descriptor; +import hudson.model.Item; import hudson.plugins.git.GitChangeSet; import hudson.plugins.git.GitChangeSet.Path; +import hudson.plugins.git.Messages; import hudson.scm.EditType; import hudson.scm.RepositoryBrowser; import hudson.util.FormValidation; import hudson.util.FormValidation.URLCheck; -import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.QueryParameter; @@ -19,6 +22,7 @@ import javax.servlet.ServletException; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; @@ -66,6 +70,7 @@ public String getProjectName() { private String encodeString(final String s) throws UnsupportedEncodingException { return URLEncoder.encode(s, "UTF-8").replaceAll("\\+", "%20"); } + @Extension public static class ViewGitWebDescriptor extends Descriptor> { @Nonnull @@ -80,18 +85,21 @@ public GitBlitRepositoryBrowser newInstance(StaplerRequest req, @Nonnull JSONObj } @RequirePOST - public FormValidation doCheckUrl(@QueryParameter(fixEmpty = true) final String url) - throws IOException, ServletException { - if (url == null) // nothing entered yet + public FormValidation doCheckRepoUrl(@AncestorInPath Item project, @QueryParameter(fixEmpty = true) final String repoUrl) + throws IOException, ServletException, URISyntaxException { + + String cleanUrl = Util.fixEmptyAndTrim(repoUrl); + if (initialChecksAndReturnOk(project, cleanUrl)) { return FormValidation.ok(); } - // Connect to URL and check content only if we have admin permission - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) - return FormValidation.ok(); + if (!checkURIFormat(cleanUrl)) + { + return FormValidation.error(Messages.invalidUrl()); + } return new URLCheck() { protected FormValidation check() throws IOException, ServletException { - String v = url; + String v = cleanUrl; if (!v.endsWith("/")) { v += '/'; } diff --git a/src/main/java/hudson/plugins/git/browser/GitRepositoryBrowser.java b/src/main/java/hudson/plugins/git/browser/GitRepositoryBrowser.java index e23661756c..b5b22d5a5d 100644 --- a/src/main/java/hudson/plugins/git/browser/GitRepositoryBrowser.java +++ b/src/main/java/hudson/plugins/git/browser/GitRepositoryBrowser.java @@ -1,12 +1,14 @@ package hudson.plugins.git.browser; import hudson.EnvVars; +import hudson.model.Item; import hudson.model.Job; import hudson.model.TaskListener; import hudson.plugins.git.GitChangeSet; import hudson.plugins.git.GitChangeSet.Path; import hudson.scm.RepositoryBrowser; +import org.apache.commons.validator.routines.UrlValidator; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; @@ -117,5 +119,25 @@ public static URL encodeURL(URL url) throws IOException { } } + protected static boolean initialChecksAndReturnOk(Item project, String cleanUrl){ + if (cleanUrl == null) { + return true; + } + if (project == null || !project.hasPermission(Item.CONFIGURE)) { + return true; + } + if (cleanUrl.contains("$")) { + // set by variable, can't validate + return true; + } + return false; + } + + protected static boolean checkURIFormat(String url) throws URISyntaxException { + String[] schemes = {"http", "https"}; + UrlValidator urlValidator = new UrlValidator(schemes); + return urlValidator.isValid(url); + } + private static final long serialVersionUID = 1L; } diff --git a/src/main/java/hudson/plugins/git/browser/Gitiles.java b/src/main/java/hudson/plugins/git/browser/Gitiles.java index 9768e4b1f2..acf32dbfa8 100644 --- a/src/main/java/hudson/plugins/git/browser/Gitiles.java +++ b/src/main/java/hudson/plugins/git/browser/Gitiles.java @@ -1,16 +1,18 @@ package hudson.plugins.git.browser; import hudson.Extension; +import hudson.Util; import hudson.model.Descriptor; +import hudson.model.Item; import hudson.plugins.git.GitChangeSet; import hudson.plugins.git.GitChangeSet.Path; +import hudson.plugins.git.Messages; import hudson.scm.RepositoryBrowser; import hudson.util.FormValidation; import hudson.util.FormValidation.URLCheck; -import jenkins.model.Jenkins; - import java.io.IOException; +import java.net.URISyntaxException; import java.net.URL; import javax.annotation.Nonnull; @@ -18,6 +20,7 @@ import net.sf.json.JSONObject; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.QueryParameter; @@ -70,15 +73,19 @@ public Gitiles newInstance(StaplerRequest req, @Nonnull JSONObject jsonObject) t } @RequirePOST - public FormValidation doCheckUrl(@QueryParameter(fixEmpty = true) final String url) throws IOException, ServletException { - if (url == null) // nothing entered yet - return FormValidation.ok(); - // Connect to URL and check content only if we have admin permission - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) + public FormValidation doCheckRepoUrl(@AncestorInPath Item project, @QueryParameter(fixEmpty = true) final String repoUrl) + throws IOException, ServletException, URISyntaxException { + + String cleanUrl = Util.fixEmptyAndTrim(repoUrl); + if(initialChecksAndReturnOk(project, cleanUrl)){ return FormValidation.ok(); + } + if (!checkURIFormat(cleanUrl)) { + return FormValidation.error(Messages.invalidUrl()); + } return new URLCheck() { protected FormValidation check() throws IOException, ServletException { - String v = url; + String v = cleanUrl; if (!v.endsWith("/")) v += '/'; diff --git a/src/main/java/hudson/plugins/git/browser/ViewGitWeb.java b/src/main/java/hudson/plugins/git/browser/ViewGitWeb.java index ad3c3cafa3..74234f8ea0 100644 --- a/src/main/java/hudson/plugins/git/browser/ViewGitWeb.java +++ b/src/main/java/hudson/plugins/git/browser/ViewGitWeb.java @@ -1,16 +1,19 @@ package hudson.plugins.git.browser; import hudson.Extension; +import hudson.Util; import hudson.model.Descriptor; +import hudson.model.Item; import hudson.plugins.git.GitChangeSet; import hudson.plugins.git.GitChangeSet.Path; +import hudson.plugins.git.Messages; import hudson.scm.EditType; import hudson.scm.RepositoryBrowser; import hudson.scm.browsers.QueryBuilder; import hudson.util.FormValidation; import hudson.util.FormValidation.URLCheck; -import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.QueryParameter; @@ -20,6 +23,7 @@ import javax.servlet.ServletException; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; @@ -89,15 +93,19 @@ public ViewGitWeb newInstance(StaplerRequest req, @Nonnull JSONObject jsonObject } @RequirePOST - public FormValidation doCheckUrl(@QueryParameter(fixEmpty = true) final String url) throws IOException, ServletException { - if (url == null) // nothing entered yet - return FormValidation.ok(); + public FormValidation doCheckRepoUrl(@AncestorInPath Item project, @QueryParameter(fixEmpty = true) final String repoUrl) + throws IOException, ServletException, URISyntaxException { + + String cleanUrl = Util.fixEmptyAndTrim(repoUrl); // Connect to URL and check content only if we have admin permission - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) + if (initialChecksAndReturnOk(project, cleanUrl)) return FormValidation.ok(); + if (!checkURIFormat(cleanUrl)) { + return FormValidation.error(Messages.invalidUrl()); + } return new URLCheck() { protected FormValidation check() throws IOException, ServletException { - String v = url; + String v = cleanUrl; if (!v.endsWith("/")) v += '/'; diff --git a/src/main/resources/hudson/plugins/git/Messages.properties b/src/main/resources/hudson/plugins/git/Messages.properties index 11401f4bd1..f64069d344 100644 --- a/src/main/resources/hudson/plugins/git/Messages.properties +++ b/src/main/resources/hudson/plugins/git/Messages.properties @@ -6,6 +6,7 @@ BuildChooser_BuildingLastRevision=No new revisions were found; the most-recently UserRemoteConfig.FailedToConnect=Failed to connect to repository : {0} UserRemoteConfig.CheckUrl.UrlIsNull=Please enter Git repository. UserRemoteConfig.CheckRefSpec.InvalidRefSpec=Specification is invalid. +invalidUrl=Invalid URL GitPublisher.Check.TagName=Tag Name GitPublisher.Check.BranchName=Branch Name diff --git a/src/test/java/hudson/plugins/git/browser/AssemblaWebDoCheckURLTest.java b/src/test/java/hudson/plugins/git/browser/AssemblaWebDoCheckURLTest.java new file mode 100644 index 0000000000..9f51080885 --- /dev/null +++ b/src/test/java/hudson/plugins/git/browser/AssemblaWebDoCheckURLTest.java @@ -0,0 +1,104 @@ +package hudson.plugins.git.browser; + +import hudson.model.FreeStyleProject; +import hudson.util.FormValidation; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +public class AssemblaWebDoCheckURLTest { + + @ClassRule + public static JenkinsRule r = new JenkinsRule(); + private static int counter = 0; + + private FreeStyleProject project; + private AssemblaWeb.AssemblaWebDescriptor assemblaWebDescriptor; + + @Before + public void setProject() throws Exception { + project = r.createFreeStyleProject("assembla-project-" + counter++); + assemblaWebDescriptor = new AssemblaWeb.AssemblaWebDescriptor(); + } + + @Test + public void testInitialChecksOnRepoUrl() throws Exception { + String url = "https://app.assembla.com/spaces/git-plugin/git/source"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url), is(FormValidation.ok())); + } + + @Test + public void testInitialChecksOnRepoUrlEmpty() throws Exception { + String url = ""; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url), is(FormValidation.ok())); + } + + @Test + public void testInitialChecksOnRepoUrlWithVariable() throws Exception { + String url = "https://www.assembla.com/spaces/$"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url), is(FormValidation.ok())); + } + + @Test + public void testDomainLevelChecksOnRepoUrl() throws Exception { + // Invalid URL, missing '/' character - Earlier it would open connection for such mistakes but now check resolves it beforehand. + String url = "https:/assembla.com"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url).getLocalizedMessage(), is("Invalid URL")); + } + + @Test + public void testDomainLevelChecksOnRepoUrlInvalidURL() throws Exception { + // Invalid URL, missing ':' character - Earlier it would open connection for such mistakes but now check resolves it beforehand. + String url = "http//assmebla"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url).getLocalizedMessage(), is("Invalid URL")); + } + + @Test + public void testPathLevelChecksOnRepoUrlInvalidPathSyntax() throws Exception { + // Invalid hostname in URL + String url = "https://assembla.comspaces/git-plugin/git/source"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url).getLocalizedMessage(), is("Invalid URL")); + } + + @Test + public void testPathLevelChecksOnRepoUrlValidURLNullProject() throws Exception { + String url = "https://app.assembla.com/space/git-plugin/git/source"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(null, url), is(FormValidation.ok())); + } + + @Test + public void testPathLevelChecksOnRepoUrlUnableToConnect() throws Exception { + // Syntax issue related specific to Assembla + String url = "https://app.assembla.com/space/git-plugin/git/source"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url).getLocalizedMessage(), + is("Unable to connect https://app.assembla.com/space/git-plugin/git/source/")); + } + + @Test + public void testPathLevelChecksOnRepoUrl() throws Exception { + // Any path related errors will not be caught except syntax issues + String url = "https://app.assembla.com/spaces/"; + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url), is(FormValidation.ok())); + } + + @Test + public void testPathLevelChecksOnRepoUrlSupersetOfAssembla() throws Exception { + java.util.Random random = new java.util.Random(); + String [] urls = { + "http://assemblage.com/", + "http://assemblage.net/", + "http://assemblage.org/", + "http://assemblages.com/", + "http://assemblages.net/", + "http://assemblages.org/", + "http://assemblagist.com/", + }; + String url = urls[random.nextInt(urls.length)]; // Don't abuse a single web site with tests + assertThat(assemblaWebDescriptor.doCheckRepoUrl(project, url).getLocalizedMessage(), + is("Invalid URL")); + } +}