From b140c81b71ba44af57c107fc55a5c9b336e90896 Mon Sep 17 00:00:00 2001 From: "Raphael A. Bauer" Date: Tue, 30 Jun 2015 01:03:23 +0200 Subject: [PATCH] Some cleanup and more tests. --- .../src/main/java/ninja/AssetsController.java | 68 +---- .../java/ninja/AssetsControllerHelper.java | 51 ++++ .../src/site/markdown/developer/changelog.md | 7 + .../ninja/AssetsControllerHelperTest.java | 64 ++++ .../test/java/ninja/AssetsControllerTest.java | 142 ++++++--- .../java/ninja/AssetsControllerTest1.java | 285 ++++++++++++++++++ .../resources/webjars/webjar_asset.txt | 1 + 7 files changed, 519 insertions(+), 99 deletions(-) create mode 100644 ninja-core/src/main/java/ninja/AssetsControllerHelper.java create mode 100644 ninja-core/src/test/java/ninja/AssetsControllerHelperTest.java create mode 100644 ninja-core/src/test/java/ninja/AssetsControllerTest1.java create mode 100644 ninja-core/src/test/resources/META-INF/resources/webjars/webjar_asset.txt diff --git a/ninja-core/src/main/java/ninja/AssetsController.java b/ninja-core/src/main/java/ninja/AssetsController.java index 36e952bf92..ec8bea908f 100644 --- a/ninja-core/src/main/java/ninja/AssetsController.java +++ b/ninja-core/src/main/java/ninja/AssetsController.java @@ -60,11 +60,15 @@ public class AssetsController { private final HttpCacheToolkit httpCacheToolkit; private final NinjaProperties ninjaProperties; + + private final AssetsControllerHelper assetsControllerHelper; @Inject - public AssetsController(HttpCacheToolkit httpCacheToolkit, + public AssetsController(AssetsControllerHelper assetsControllerHelper, + HttpCacheToolkit httpCacheToolkit, MimeTypes mimeTypes, NinjaProperties ninjaProperties) { + this.assetsControllerHelper = assetsControllerHelper; this.httpCacheToolkit = httpCacheToolkit; this.mimeTypes = mimeTypes; this.ninjaProperties = ninjaProperties; @@ -86,7 +90,7 @@ public AssetsController(HttpCacheToolkit httpCacheToolkit, * /assets/app/app.css (from your jar). * */ - public Result serveStatic(Context context) { + public Result serveStatic() { Object renderable = new Renderable() { @Override public void render(Context context, Result result) { @@ -107,7 +111,7 @@ public void render(Context context, Result result) { * Request to /public/css/app.css will be served from /assets/css/app.css. * */ - public Result serveWebJars(Context context) { + public Result serveWebJars() { Object renderable = new Renderable() { @Override public void render(Context context, Result result) { @@ -147,14 +151,12 @@ private void streamOutUrlEntity(URL url, Context context, Result result) { .finalizeHeadersWithoutFlashAndSessionCookie(result); try (InputStream inputStream = urlConnection.getInputStream(); - OutputStream outputStream = responseStreams.getOutputStream()) { + OutputStream outputStream = responseStreams.getOutputStream()) { ByteStreams.copy(inputStream, outputStream); } } - } catch (FileNotFoundException e) { - logger.error("error streaming file", e); } catch (IOException e) { logger.error("error streaming file", e); } @@ -170,22 +172,18 @@ private void streamOutUrlEntity(URL url, Context context, Result result) { * be overridden by static.asset.base.dir in application conf file. */ private URL getStaticFileFromAssetsDir(String fileName) { - - String finalNameWithoutLeadingSlash = normalizePathWithoutLeadingSlash(fileName); URL url = null; - if (ninjaProperties.isDev()) { + if (ninjaProperties.isDev()) { + String finalNameWithoutLeadingSlash = assetsControllerHelper.normalizePathWithoutLeadingSlash(fileName, false); File possibleFile = new File( assetsDirInDevModeWithoutTrailingSlash() + File.separator + finalNameWithoutLeadingSlash); url = getUrlForFile(possibleFile); } else { - // In mode test and prod, if static.asset.base.dir not specified then we stream via the classloader. - // - // In dev mode: If we cannot find the file in src we are also looking for the file - // on the classpath (can be the case for plugins that ship their own assets. + String finalNameWithoutLeadingSlash = assetsControllerHelper.normalizePathWithoutLeadingSlash(fileName, true); url = this.getClass().getClassLoader() .getResource(ASSETS_DIR + "/" @@ -214,52 +212,12 @@ private URL getUrlForFile(File possibleFileInSrc) { */ private URL getStaticFileFromMetaInfResourcesDir(String fileName) { String finalNameWithoutLeadingSlash - = normalizePathWithoutLeadingSlash(fileName, true); - - URL url = null; - + = assetsControllerHelper.normalizePathWithoutLeadingSlash(fileName, true); + URL url = null; url = this.getClass().getClassLoader().getResource("META-INF/resources/webjars/" + finalNameWithoutLeadingSlash); - return url; } - /** - * This function mirrors legacy behavior of the AssetsController - * - * @param fileName A potential "fileName" - * @see #normalizePathWithoutLeadingSlash(java.lang.String, boolean) - * @deprecated This method was replaced and should not be used. - * @return A normalized fileName. - */ - @VisibleForTesting - @Deprecated - protected String normalizePathWithoutLeadingSlash(String fileName) { - return normalizePathWithoutLeadingSlash(fileName, false); - } - - /** - * If we get - for whatever reason - a relative URL like - * assets/../conf/application.conf we expand that to the "real" path. - * In the above case conf/application.conf. - * - * You should then add the assets prefix. - * - * Otherwise someone can create an attack and read all resources of our - * app. If we expand and normalize the incoming path this is no longer - * possible. - * - * @param fileName A potential "fileName" - * @param enforceUnixSeparator Forces to normalise unix style - * @return A normalized fileName. - */ - @VisibleForTesting - protected String normalizePathWithoutLeadingSlash(String fileName, boolean enforceUnixSeparator) { - String fileNameNormalized = enforceUnixSeparator - ? FilenameUtils.normalize(fileName, true) - : FilenameUtils.normalize(fileName); - return StringUtils.removeStart(fileNameNormalized, "/"); - } - private static String getFileNameFromPathOrReturnRequestPath(Context context) { String fileName = context.getPathParameter(FILENAME_PATH_PARAM); diff --git a/ninja-core/src/main/java/ninja/AssetsControllerHelper.java b/ninja-core/src/main/java/ninja/AssetsControllerHelper.java new file mode 100644 index 0000000000..72b1e6f338 --- /dev/null +++ b/ninja-core/src/main/java/ninja/AssetsControllerHelper.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package ninja; + +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.commons.lang.StringUtils; + +public class AssetsControllerHelper { + + private final static Logger logger = LoggerFactory + .getLogger(AssetsControllerHelper.class); + + /** + * If we get - for whatever reason - a relative URL like + * assets/../conf/application.conf we expand that to the "real" path. In the + * above case conf/application.conf. + * + * You should then add the assets prefix. + * + * Otherwise someone can create an attack and read all resources of our app. + * If we expand and normalize the incoming path this is no longer possible. + * + * @param fileName A potential "fileName" + * @param enforceUnixSeparator If true it will force the usage of the unix separator '/' + * If false it will use the separator of the underlying system. + * usually '/' in case of unix and '\' in case of windows. + * @return A normalized fileName. + */ + public String normalizePathWithoutLeadingSlash(String fileName, boolean enforceUnixSeparator) { + String fileNameNormalized = enforceUnixSeparator + ? FilenameUtils.normalize(fileName, true) + : FilenameUtils.normalize(fileName); + return StringUtils.removeStart(fileNameNormalized, "/"); + } +} diff --git a/ninja-core/src/site/markdown/developer/changelog.md b/ninja-core/src/site/markdown/developer/changelog.md index c7048ca66a..8415323a9e 100644 --- a/ninja-core/src/site/markdown/developer/changelog.md +++ b/ninja-core/src/site/markdown/developer/changelog.md @@ -1,3 +1,10 @@ +Version X.X.X +============= + + * 2015-06-30 Fix for asset controller incompatibility with windows file system (BjoernAkAManf). + * 2015-06-30 Improved tests in asset controller for serving webjars (ra). + + Version 5.1.3 ============= diff --git a/ninja-core/src/test/java/ninja/AssetsControllerHelperTest.java b/ninja-core/src/test/java/ninja/AssetsControllerHelperTest.java new file mode 100644 index 0000000000..209fa0dc8a --- /dev/null +++ b/ninja-core/src/test/java/ninja/AssetsControllerHelperTest.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package ninja; + +import static org.junit.Assert.assertEquals; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import org.apache.commons.io.FilenameUtils; +import org.junit.Before; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(FilenameUtils.class) +public class AssetsControllerHelperTest { + + AssetsControllerHelper assetsControllerHelper; + + @Before + public void setup() { + assetsControllerHelper = new AssetsControllerHelper(); + } + + @Test + public void testNormalizePathWithoutLeadingSlash() { + assertEquals("dir1/test.test", assetsControllerHelper.normalizePathWithoutLeadingSlash("/dir1/test.test", true)); + assertEquals("dir1/test.test", assetsControllerHelper.normalizePathWithoutLeadingSlash("dir1/test.test", true)); + assertEquals(null, assetsControllerHelper.normalizePathWithoutLeadingSlash("/../test.test", true)); + assertEquals(null, assetsControllerHelper.normalizePathWithoutLeadingSlash("../test.test", true)); + assertEquals("dir2/file.test", assetsControllerHelper.normalizePathWithoutLeadingSlash("/dir1/../dir2/file.test", true)); + assertEquals(null, assetsControllerHelper.normalizePathWithoutLeadingSlash(null, true)); + assertEquals("", assetsControllerHelper.normalizePathWithoutLeadingSlash("", true)); + } + + @Test + public void testNormalizePathWithoutLeadingSlashCorrectFilnameUtilStaticMethodsCalled() { + PowerMockito.mockStatic(FilenameUtils.class, Mockito.CALLS_REAL_METHODS); + + assetsControllerHelper.normalizePathWithoutLeadingSlash("/dir1/test.test", false); + PowerMockito.verifyStatic(); + FilenameUtils.normalize("/dir1/test.test"); + + assetsControllerHelper.normalizePathWithoutLeadingSlash("/dir1/test.test", true); + PowerMockito.verifyStatic(); + FilenameUtils.normalize("/dir1/test.test", true); + } +} diff --git a/ninja-core/src/test/java/ninja/AssetsControllerTest.java b/ninja-core/src/test/java/ninja/AssetsControllerTest.java index f083f0fd76..0000583559 100644 --- a/ninja-core/src/test/java/ninja/AssetsControllerTest.java +++ b/ninja-core/src/test/java/ninja/AssetsControllerTest.java @@ -17,12 +17,8 @@ package ninja; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.verifyStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.anyString; import java.io.ByteArrayOutputStream; @@ -30,7 +26,6 @@ import ninja.utils.MimeTypes; import ninja.utils.NinjaProperties; import ninja.utils.ResponseStreams; -import org.apache.commons.io.FilenameUtils; import org.junit.Before; import org.junit.Test; @@ -39,13 +34,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; +import org.mockito.runners.MockitoJUnitRunner; -@RunWith(PowerMockRunner.class) -@PrepareForTest(FilenameUtils.class) +@RunWith(MockitoJUnitRunner.class) public class AssetsControllerTest { @Mock @@ -71,7 +62,10 @@ public class AssetsControllerTest { @Before public void before() { assetsController = new AssetsController( - httpCacheToolkit, mimeTypes, ninjaProperties); + new AssetsControllerHelper(), + httpCacheToolkit, + mimeTypes, + ninjaProperties); } @@ -79,7 +73,7 @@ public void before() { @Test public void testServeStatic404() throws Exception { when(contextRenderable.getRequestPath()).thenReturn("notAvailable"); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -95,7 +89,7 @@ public void testServeStatic404() throws Exception { @Test public void testServeStaticSecurityClassesWithoutSlash() throws Exception { when(contextRenderable.getRequestPath()).thenReturn("ninja/Ninja.class"); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -112,7 +106,7 @@ public void testServeStaticSecurityClassesWithoutSlash() throws Exception { public void testServeStaticSecurityClassesAbsolute() throws Exception { when(contextRenderable.getRequestPath()).thenReturn("/ninja/Ninja.class"); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -131,7 +125,7 @@ public void testServeStaticSecurityNoRelativPathWorks() throws Exception { // But it should when(contextRenderable.getRequestPath()).thenReturn("/assets/../../conf/heroku.conf"); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -149,7 +143,7 @@ public void testServeStatic304NotModified() throws Exception { when(contextRenderable.getRequestPath()).thenReturn( "/assets/testasset.txt"); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -173,6 +167,57 @@ public void testServeStatic304NotModified() throws Exception { } + @Test + public void testStaticDirectoryIsFileSystemInDevMode() throws Exception { + + // some more setup needed: + Mockito.when(ninjaProperties.isDev()).thenReturn(true); + AssetsControllerHelper assetsControllerHelper = Mockito.mock(AssetsControllerHelper.class, Mockito.CALLS_REAL_METHODS); + + assetsController = new AssetsController( + assetsControllerHelper, + httpCacheToolkit, + mimeTypes, + ninjaProperties); + + when(contextRenderable.getRequestPath()).thenReturn( + "/assets/testasset.txt"); + + Result result2 = assetsController.serveStatic(); + + Renderable renderable = (Renderable) result2.getRenderable(); + renderable.render(contextRenderable, Results.ok()); + verify(assetsControllerHelper).normalizePathWithoutLeadingSlash("/assets/testasset.txt", false); + + } + + @Test + public void testStaticDirectoryIsClassPathInProdMode() throws Exception { + + // some more setup needed: + Mockito.when(ninjaProperties.isDev()).thenReturn(false); + AssetsControllerHelper assetsControllerHelper = Mockito.mock(AssetsControllerHelper.class, Mockito.CALLS_REAL_METHODS); + assetsController = new AssetsController( + assetsControllerHelper, + httpCacheToolkit, + mimeTypes, + ninjaProperties); + when(contextRenderable.getRequestPath()).thenReturn( + "/assets/testasset.txt"); + when(contextRenderable.finalizeHeadersWithoutFlashAndSessionCookie(Mockito.any(Result.class))).thenReturn( + responseStreams); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + when(responseStreams.getOutputStream()).thenReturn( + byteArrayOutputStream); + + Result result2 = assetsController.serveStatic(); + + Renderable renderable = (Renderable) result2.getRenderable(); + renderable.render(contextRenderable, Results.ok()); + verify(assetsControllerHelper).normalizePathWithoutLeadingSlash("/assets/testasset.txt", true); + + } + @Test public void testServeStaticNormalOperationModifiedNoCaching() throws Exception { @@ -191,7 +236,7 @@ public void testServeStaticNormalOperationModifiedNoCaching() ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); when(responseStreams.getOutputStream()).thenReturn( byteArrayOutputStream); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -232,7 +277,7 @@ public void testServeStaticRobotsTxt() ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); when(responseStreams.getOutputStream()).thenReturn( byteArrayOutputStream); - Result result2 = assetsController.serveStatic(null); + Result result2 = assetsController.serveStatic(); Renderable renderable = (Renderable) result2.getRenderable(); @@ -255,31 +300,40 @@ public void testServeStaticRobotsTxt() assertEquals("User-agent: *" + sysLineSeparator + "Disallow: /", byteArrayOutputStream.toString()); } - + @Test - public void testNormalizePathWithoutLeadingSlash() { - assertEquals("dir1/test.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/test.test", true)); - assertEquals("dir1/test.test", assetsController.normalizePathWithoutLeadingSlash("dir1/test.test", true)); - assertEquals(null, assetsController.normalizePathWithoutLeadingSlash("/../test.test", true)); - assertEquals(null, assetsController.normalizePathWithoutLeadingSlash("../test.test", true)); - assertEquals("dir2/file.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/../dir2/file.test", true)); - assertEquals(null, assetsController.normalizePathWithoutLeadingSlash(null, true)); - assertEquals("", assetsController.normalizePathWithoutLeadingSlash("", true)); - - // enforcing test for unix separator | Windows specific - mockStatic(FilenameUtils.class, Mockito.CALLS_REAL_METHODS); - - when(FilenameUtils.normalize(anyString())).then(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - String file = (String) args[0]; - return FilenameUtils.normalize(file, false); // Choose Windows here, despite we may test on unix - } - }); - - assertEquals("\\dir1\\test.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/test.test", false)); - assertNotEquals("\\dir1\\test.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/test.test", true)); - verifyStatic(); + public void testServeWebJars() throws Exception { + AssetsControllerHelper assetsControllerHelper + = Mockito.mock(AssetsControllerHelper.class, Mockito.CALLS_REAL_METHODS); + assetsController = new AssetsController( + assetsControllerHelper, + httpCacheToolkit, + mimeTypes, + ninjaProperties); + Result result = Results.ok(); + + when(contextRenderable.getRequestPath()).thenReturn( + "/webjar_asset.txt"); + + when(contextRenderable.finalizeHeadersWithoutFlashAndSessionCookie(Mockito.eq(result))).thenReturn( + responseStreams); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + when(responseStreams.getOutputStream()).thenReturn( + byteArrayOutputStream); + Result result2 = assetsController.serveWebJars(); + + Renderable renderable = (Renderable) result2.getRenderable(); + + renderable.render(contextRenderable, result); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + + // make sure we get the correct result... + assertEquals(Result.SC_200_OK, resultCaptor.getValue().getStatusCode()); + + assertEquals("webjar_asset", byteArrayOutputStream.toString()); + verify(assetsControllerHelper).normalizePathWithoutLeadingSlash("/webjar_asset.txt", true); + } } \ No newline at end of file diff --git a/ninja-core/src/test/java/ninja/AssetsControllerTest1.java b/ninja-core/src/test/java/ninja/AssetsControllerTest1.java new file mode 100644 index 0000000000..f083f0fd76 --- /dev/null +++ b/ninja-core/src/test/java/ninja/AssetsControllerTest1.java @@ -0,0 +1,285 @@ +/** + * Copyright (C) 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ninja; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.anyString; + +import java.io.ByteArrayOutputStream; + +import ninja.utils.HttpCacheToolkit; +import ninja.utils.MimeTypes; +import ninja.utils.NinjaProperties; +import ninja.utils.ResponseStreams; +import org.apache.commons.io.FilenameUtils; +import org.junit.Before; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(FilenameUtils.class) +public class AssetsControllerTest { + + @Mock + MimeTypes mimeTypes; + + @Mock + HttpCacheToolkit httpCacheToolkit; + + @Mock + Context contextRenderable; + + @Captor + ArgumentCaptor resultCaptor; + + @Mock + ResponseStreams responseStreams; + + @Mock + NinjaProperties ninjaProperties; + + AssetsController assetsController; + + @Before + public void before() { + assetsController = new AssetsController( + httpCacheToolkit, mimeTypes, ninjaProperties); + } + + + + @Test + public void testServeStatic404() throws Exception { + when(contextRenderable.getRequestPath()).thenReturn("notAvailable"); + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + Result result = Results.ok(); + + renderable.render(contextRenderable, result); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + assertEquals(Results.notFound().getStatusCode(), resultCaptor.getValue().getStatusCode()); + + } + + @Test + public void testServeStaticSecurityClassesWithoutSlash() throws Exception { + when(contextRenderable.getRequestPath()).thenReturn("ninja/Ninja.class"); + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + Result result = Results.ok(); + + renderable.render(contextRenderable, result); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + assertEquals(Results.notFound().getStatusCode(), resultCaptor.getValue().getStatusCode()); + + } + + @Test + public void testServeStaticSecurityClassesAbsolute() throws Exception { + + when(contextRenderable.getRequestPath()).thenReturn("/ninja/Ninja.class"); + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + Result result = Results.ok(); + + renderable.render(contextRenderable, result); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + assertEquals(Results.notFound().getStatusCode(), resultCaptor.getValue().getStatusCode()); + + } + + @Test + public void testServeStaticSecurityNoRelativPathWorks() throws Exception { + //This theoretically could work as robots.txt is there.. + // But it should + when(contextRenderable.getRequestPath()).thenReturn("/assets/../../conf/heroku.conf"); + + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + Result result = Results.ok(); + + renderable.render(contextRenderable, result); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + assertEquals(Results.notFound().getStatusCode(), resultCaptor.getValue().getStatusCode()); + } + + @Test + public void testServeStatic304NotModified() throws Exception { + + when(contextRenderable.getRequestPath()).thenReturn( + "/assets/testasset.txt"); + + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + Result result = Results.ok(); + // manually set to not modified => asset controller should + // only finalize, but not stream + result.status(Result.SC_304_NOT_MODIFIED); + + renderable.render(contextRenderable, result); + // test streaming of resource: + // => not modified: + // check etag has been called + verify(httpCacheToolkit).addEtag(Mockito.eq(contextRenderable), + Mockito.eq(result), Mockito.anyLong()); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + + // make sure we get the correct result... + assertEquals(Result.SC_304_NOT_MODIFIED, resultCaptor.getValue() + .getStatusCode()); + + } + + @Test + public void testServeStaticNormalOperationModifiedNoCaching() + throws Exception { + + Result result = Results.ok(); + + when(contextRenderable.getRequestPath()).thenReturn( + "/assets/testasset.txt"); + + when(mimeTypes.getContentType(Mockito.eq(contextRenderable), + Mockito.anyString())).thenReturn("mimetype"); + + when(contextRenderable.finalizeHeadersWithoutFlashAndSessionCookie(Mockito.eq(result))).thenReturn( + responseStreams); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + when(responseStreams.getOutputStream()).thenReturn( + byteArrayOutputStream); + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + renderable.render(contextRenderable, result); + // test streaming of resource: + // => not modified: + // check etag has been called + verify(httpCacheToolkit).addEtag(Mockito.eq(contextRenderable), + Mockito.eq(result), Mockito.anyLong()); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + + // make sure we get the correct result... + assertEquals(Result.SC_200_OK, resultCaptor.getValue().getStatusCode()); + // we mocked this one: + assertEquals("mimetype", result.getContentType()); + + // make sure the content is okay... + assertEquals("testasset", byteArrayOutputStream.toString()); + + } + + @Test + public void testServeStaticRobotsTxt() + throws Exception { + + Result result = Results.ok(); + + when(contextRenderable.getRequestPath()).thenReturn( + "/robots.txt"); + + when(mimeTypes.getContentType(Mockito.eq(contextRenderable), + Mockito.anyString())).thenReturn("mimetype"); + + when(contextRenderable.finalizeHeadersWithoutFlashAndSessionCookie(Mockito.eq(result))).thenReturn( + responseStreams); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + when(responseStreams.getOutputStream()).thenReturn( + byteArrayOutputStream); + Result result2 = assetsController.serveStatic(null); + + Renderable renderable = (Renderable) result2.getRenderable(); + + renderable.render(contextRenderable, result); + // test streaming of resource: + // => not modified: + // check etag has been called + verify(httpCacheToolkit).addEtag(Mockito.eq(contextRenderable), + Mockito.eq(result), Mockito.anyLong()); + + verify(contextRenderable).finalizeHeadersWithoutFlashAndSessionCookie(resultCaptor.capture()); + + // make sure we get the correct result... + assertEquals(Result.SC_200_OK, resultCaptor.getValue().getStatusCode()); + // we mocked this one: + assertEquals("mimetype", result.getContentType()); + + // make sure the content is okay but pay attention to system specific line separator + String sysLineSeparator = System.lineSeparator(); + assertEquals("User-agent: *" + sysLineSeparator + "Disallow: /", byteArrayOutputStream.toString()); + + } + + @Test + public void testNormalizePathWithoutLeadingSlash() { + assertEquals("dir1/test.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/test.test", true)); + assertEquals("dir1/test.test", assetsController.normalizePathWithoutLeadingSlash("dir1/test.test", true)); + assertEquals(null, assetsController.normalizePathWithoutLeadingSlash("/../test.test", true)); + assertEquals(null, assetsController.normalizePathWithoutLeadingSlash("../test.test", true)); + assertEquals("dir2/file.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/../dir2/file.test", true)); + assertEquals(null, assetsController.normalizePathWithoutLeadingSlash(null, true)); + assertEquals("", assetsController.normalizePathWithoutLeadingSlash("", true)); + + // enforcing test for unix separator | Windows specific + mockStatic(FilenameUtils.class, Mockito.CALLS_REAL_METHODS); + + when(FilenameUtils.normalize(anyString())).then(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + String file = (String) args[0]; + return FilenameUtils.normalize(file, false); // Choose Windows here, despite we may test on unix + } + }); + + assertEquals("\\dir1\\test.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/test.test", false)); + assertNotEquals("\\dir1\\test.test", assetsController.normalizePathWithoutLeadingSlash("/dir1/test.test", true)); + verifyStatic(); + } +} \ No newline at end of file diff --git a/ninja-core/src/test/resources/META-INF/resources/webjars/webjar_asset.txt b/ninja-core/src/test/resources/META-INF/resources/webjars/webjar_asset.txt new file mode 100644 index 0000000000..3d8cf0b862 --- /dev/null +++ b/ninja-core/src/test/resources/META-INF/resources/webjars/webjar_asset.txt @@ -0,0 +1 @@ +webjar_asset \ No newline at end of file