diff --git a/core/pom.xml b/core/pom.xml index 6758f8947de585c45402d32795adb660dfd4d4f9..ab09282028a0be91a2a4d0528f9b2ff628f02e4b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -425,6 +425,28 @@ THE SOFTWARE. junit-dep test + + org.mockito + mockito-core + test + + + org.mockito + mockito-core + test + + + org.powermock + powermock-module-junit4 + 1.4.10 + test + + + org.powermock + powermock-api-mockito + 1.4.10 + test + javax.servlet jstl diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 741c02fa1e941748ff7be75702e8285a848b2833..4ba8aad6cdd354d136a92e463cd14ad62370b5c6 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -1169,7 +1169,7 @@ public class Functions { public static String getActionUrl(String itUrl,Action action) { String urlName = action.getUrlName(); if(urlName==null) return null; // to avoid NPE and fail to render the whole page - if(SCHEME.matcher(urlName).matches()) + if(SCHEME.matcher(urlName).find()) return urlName; // absolute URL if(urlName.startsWith("/")) return Stapler.getCurrentRequest().getContextPath()+urlName; @@ -1363,7 +1363,7 @@ public class Functions { return DescriptorVisibilityFilter.apply(context,descriptors); } - private static final Pattern SCHEME = Pattern.compile("[a-z]+://.+"); + private static final Pattern SCHEME = Pattern.compile("^([a-zA-Z][a-zA-Z0-9+.-]*):"); /** * Returns true if we are running unit tests. diff --git a/core/src/test/java/hudson/FunctionsTest.java b/core/src/test/java/hudson/FunctionsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fe8db9f16334ad04384cf2c7253dd61bc1f25476 --- /dev/null +++ b/core/src/test/java/hudson/FunctionsTest.java @@ -0,0 +1,114 @@ +/* + * The MIT License + * + * Copyright 2011, OHTAKE Tomohiro. + * + * 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; + +import hudson.model.Action; +import static org.junit.Assert.*; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.jvnet.hudson.test.Bug; +import org.kohsuke.stapler.Stapler; +import org.kohsuke.stapler.StaplerRequest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.core.classloader.annotations.PrepareForTest; +import static org.powermock.api.mockito.PowerMockito.*; + +@RunWith(PowerMockRunner.class) +public class FunctionsTest { + @Test + public void testGetActionUrl_absoluteUriWithAuthority(){ + String[] uris = { + "http://example.com/foo/bar", + "https://example.com/foo/bar", + "ftp://example.com/foo/bar", + "svn+ssh://nobody@example.com/foo/bar", + }; + for(String uri : uris) { + String result = Functions.getActionUrl(null, createMockAction(uri)); + assertEquals(uri, result); + } + } + + @Test + @Bug(7725) + public void testGetActionUrl_absoluteUriWithoutAuthority(){ + String[] uris = { + "mailto:nobody@example.com", + "mailto:nobody@example.com?subject=hello", + "javascript:alert('hello')", + }; + for(String uri : uris) { + String result = Functions.getActionUrl(null, createMockAction(uri)); + assertEquals(uri, result); + } + } + + @Test + @PrepareForTest(Stapler.class) + public void testGetActionUrl_absolutePath() throws Exception{ + String contextPath = "/jenkins"; + StaplerRequest req = createMockRequest(contextPath); + String[] paths = { + "/", + "/foo/bar", + }; + mockStatic(Stapler.class); + when(Stapler.getCurrentRequest()).thenReturn(req); + for(String path : paths) { + String result = Functions.getActionUrl(null, createMockAction(path)); + assertEquals(contextPath + path, result); + } + } + + @Test + @PrepareForTest(Stapler.class) + public void testGetActionUrl_relativePath() throws Exception{ + String contextPath = "/jenkins"; + String itUrl = "iturl/"; + StaplerRequest req = createMockRequest(contextPath); + String[] paths = { + "foo/bar", + "./foo/bar", + "../foo/bar", + }; + mockStatic(Stapler.class); + when(Stapler.getCurrentRequest()).thenReturn(req); + for(String path : paths) { + String result = Functions.getActionUrl(itUrl, createMockAction(path)); + assertEquals(contextPath + "/" + itUrl + path, result); + } + } + + private static Action createMockAction(String uri) { + Action action = mock(Action.class); + when(action.getUrlName()).thenReturn(uri); + return action; + } + + private static StaplerRequest createMockRequest(String contextPath) { + StaplerRequest req = mock(StaplerRequest.class); + when(req.getContextPath()).thenReturn(contextPath); + return req; + } +}