/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.xpn.xwiki.web; import java.io.File; import java.io.IOException; import javax.servlet.ServletOutputStream; import org.apache.commons.io.FileUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; import org.xwiki.environment.Environment; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.test.MockitoOldcoreRule; import com.xpn.xwiki.web.includeservletasstring.BufferOutputStream; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Unit tests for {@link TempResourceAction}. * * @version $Id: 31e0b72cce06eacb2c499dd991bb3585e4c83b27 $ */ public class TempResourceActionTest { @Rule public MockitoOldcoreRule oldcore = new MockitoOldcoreRule(); /** * The action being tested. */ private TempResourceAction action; /** * The base directory. */ private File base; @Before public void setUp() throws Exception { base = new File(getClass().getResource("/").toURI()); // Configure Servlet Environment defined in AbstractBridgedComponentTestCase so that it returns a good // temporary directory Environment environment = oldcore.getMocker().registerMockComponent(Environment.class); when(environment.getTemporaryDirectory()).thenReturn(base); action = new TempResourceAction(); } /** * Creates an empty file at the specified path. * * @param path the file path * @throws IOException if creating the empty file fails */ private void createEmptyFile(String path) throws IOException { File emptyFile = new File(base, path); emptyFile.getParentFile().mkdirs(); emptyFile.createNewFile(); emptyFile.deleteOnExit(); } /** * Creates a file at the specified path, with the specified content. * * @param path the file path * @throws IOException if creating the empty file fails */ private void createFile(String path, String content) throws IOException { File file = new File(base, path); file.getParentFile().mkdirs(); file.createNewFile(); file.deleteOnExit(); FileUtils.write(file, content); } /** * {@link TempResourceAction#getTemporaryFile(String, XWikiContext)} should return {@code null} if the given URI * doesn't match the known pattern. */ @Test public void testGetTemporaryFileForBadURI() throws Exception { createEmptyFile("temp/secret.txt"); Assert.assertNull(action.getTemporaryFile("/xwiki/bin/temp/secret.txt", oldcore.getXWikiContext())); } /** * {@link TempResourceAction#getTemporaryFile(String, XWikiContext)} should prevent access to files outside the * temporary directory by ignoring relative URIs (i.e. which use ".." to move to the parent folder). */ @Test public void testGetTemporaryFileForRelativeURI() throws Exception { createEmptyFile("temp/secret.txt"); Assert.assertNull(action.getTemporaryFile("/xwiki/bin/temp/../../module/secret.txt", oldcore.getXWikiContext())); } /** * Tests {@link TempResourceAction#getTemporaryFile(String, XWikiContext)} when the file is missing. */ @Test public void testGetTemporaryFileMissing() throws Exception { Assert.assertFalse(new File(base, "temp/module/xwiki/Space/Page/file.txt").exists()); Assert.assertNull(action.getTemporaryFile("/xwiki/bin/temp/Space/Page/module/file.txt", oldcore.getXWikiContext())); } /** * Tests {@link TempResourceAction#getTemporaryFile(String, XWikiContext)} when the file is present. */ @Test public void testGetTemporaryFile() throws Exception { oldcore.getXWikiContext().setWikiId("wiki"); createEmptyFile("temp/module/wiki/Space/Page/file.txt"); Assert.assertNotNull(action.getTemporaryFile("/xwiki/bin/temp/Space/Page/module/file.txt", oldcore.getXWikiContext())); } /** * Tests {@link TempResourceAction#getTemporaryFile(String, XWikiContext)} when the URL is over encoded. */ @Test public void testGetTemporaryFileForOverEncodedURL() throws Exception { createEmptyFile("temp/officeviewer/xwiki/Sp*ace/Pa-ge/presentation.odp/presentation-slide0.jpg"); Assert.assertNotNull(action.getTemporaryFile( "/xwiki/bin/temp/Sp%2Aace/Pa%2Dge/officeviewer/presentation.odp/presentation-slide0.jpg", oldcore.getXWikiContext())); } /** * Tests {@link TempResourceAction#getTemporaryFile(String, XWikiContext)} when the URL is partially decoded. This * can happen for instance when XWiki is behind Apache's {@code mode_proxy} with {@code nocanon} option disabled. */ @Test public void testGetTemporaryFileForPartiallyDecodedURL() throws Exception { createEmptyFile("temp/officeviewer/xwiki/Space/Page/" + "attach%3Axwiki%3ASpace.Page%40pres%2Fentation.odp/13/presentation-slide0.jpg"); Assert.assertNotNull(action.getTemporaryFile("/xwiki/bin/temp/Space/Page/officeviewer/" + "attach:xwiki:Space.Page@pres%2Fentation.odp/13/presentation-slide0.jpg", oldcore.getXWikiContext())); } @Test public void renderNormalBehavior() throws Exception { XWikiRequest request = mock(XWikiRequest.class); XWikiResponse response = mock(XWikiResponse.class); BufferOutputStream out = new BufferOutputStream(); oldcore.getXWikiContext().setRequest(request); oldcore.getXWikiContext().setResponse(response); when(request.getRequestURI()).thenReturn("/xwiki/bin/temp/Space/Page/module/file.txt"); when(response.getOutputStream()).thenReturn(out); oldcore.getXWikiContext().setWikiId("wiki"); createFile("temp/module/wiki/Space/Page/file.txt", "Hello World!"); action.render(oldcore.getXWikiContext()); Assert.assertArrayEquals("Hello World!".getBytes(), out.getContentsAsByteArray()); Mockito.verify(response, Mockito.never()).addHeader("Content-disposition", "attachment; filename*=utf-8''file.txt"); } @Test public void renderWithForceDownload() throws Exception { XWikiRequest request = mock(XWikiRequest.class); XWikiResponse response = mock(XWikiResponse.class); oldcore.getXWikiContext().setRequest(request); oldcore.getXWikiContext().setResponse(response); when(request.getRequestURI()).thenReturn("/xwiki/bin/temp/Space/Page/module/file.txt"); when(request.getParameter("force-download")).thenReturn("1"); when(response.getOutputStream()).thenReturn(mock(ServletOutputStream.class)); oldcore.getXWikiContext().setWikiId("wiki"); createEmptyFile("temp/module/wiki/Space/Page/file.txt"); action.render(oldcore.getXWikiContext()); Mockito.verify(response).addHeader("Content-disposition", "attachment; filename*=utf-8''file.txt"); } @Test(expected=XWikiException.class) public void renderWithInvalidPathThrowsException() throws XWikiException { XWikiRequest request = mock(XWikiRequest.class); XWikiResponse response = mock(XWikiResponse.class); oldcore.getXWikiContext().setRequest(request); oldcore.getXWikiContext().setResponse(response); when(request.getRequestURI()).thenReturn("/xwiki/bin/temp/Space/Page/module/nosuchfile.txt"); oldcore.getXWikiContext().setWikiId("wiki"); action.render(oldcore.getXWikiContext()); } }