| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.wicket.protocol.http; |
| |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.mockito.Mockito.*; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.TimeZone; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import javax.servlet.FilterChain; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.apache.wicket.Application; |
| import org.apache.wicket.ThreadContext; |
| import org.apache.wicket.mock.MockApplication; |
| import org.apache.wicket.protocol.http.mock.MockHttpServletRequest; |
| import org.apache.wicket.protocol.http.mock.MockHttpServletResponse; |
| import org.apache.wicket.protocol.http.mock.MockServletContext; |
| import org.apache.wicket.request.http.WebRequest; |
| import org.apache.wicket.request.resource.AbstractResource; |
| import org.apache.wicket.request.resource.DynamicImageResource; |
| import org.apache.wicket.request.resource.IResource; |
| import org.apache.wicket.util.SlowTests; |
| import org.apache.wicket.util.file.WebXmlFile; |
| import org.apache.wicket.util.string.Strings; |
| import org.apache.wicket.util.tester.DummyHomePage; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.mockito.Matchers; |
| import org.mockito.Mockito; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.stubbing.Answer; |
| import org.xml.sax.SAXException; |
| |
| /** |
| */ |
| public class WicketFilterTest extends Assert |
| { |
| private static WebApplication application; |
| private final DateFormat headerDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", |
| Locale.UK); |
| |
| /** |
| * @throws Exception |
| */ |
| @After |
| public void after() throws Exception |
| { |
| if (application != null) |
| { |
| application.internalDestroy(); |
| application = null; |
| } |
| } |
| |
| /** |
| * Test for WICKET-5980 When using Servlet 3.0 filter Wicket calculates filter path wrong. |
| * |
| * When using a servlet 3.0 filter with annotations Wicket calculates the filter path wrong |
| * causing it to not match any pages other than the home page. e.g. |
| * |
| * <pre> |
| * @WebFilter(value = "/web/*", initParams = { |
| * @WebInitParam(name = "applicationClassName", value = "com.example.CheesrApplication") }) |
| * public class CheesrFilter extends WicketFilter |
| * { |
| * } |
| * </pre> |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void parsingOfAnnotatedServlet3FiltersWorks() throws Exception |
| { |
| FilterTestingConfig config = new FilterTestingConfig(); |
| config.initParameters.clear(); |
| config.initParameters.put("applicationClassName", "org.apache.wicket.mock.MockApplication"); |
| |
| WicketFilter filter = new AnnotatedServlet3Filter(); |
| // creates an Application |
| filter.init(config); |
| |
| // get a reference to the application, so that @After is able to clean it up |
| application = filter.getApplication(); |
| |
| // assert that the filter path is not /web/*/ |
| assertThat(filter.getFilterPath(), is("web/")); |
| } |
| |
| /** |
| * testFilterPath1() |
| */ |
| @Test |
| @Category(SlowTests.class) |
| public void filterPath1() |
| { |
| InputStream in = WicketFilterTest.class.getResourceAsStream("web1.xml"); |
| String filterPath = getFilterPath("FilterTestApplication", in); |
| assertEquals("filtertest/", filterPath); |
| } |
| |
| /** |
| * testFilterPath2() |
| */ |
| @Test |
| @Category(SlowTests.class) |
| public void filterPath2() |
| { |
| InputStream in = WicketFilterTest.class.getResourceAsStream("web2.xml"); |
| String filterPath = getFilterPath("FilterTestApplication", in); |
| assertEquals("filtertest/", filterPath); |
| } |
| |
| /** |
| * @throws IOException |
| * @throws ServletException |
| * @throws ParseException |
| */ |
| @Test |
| public void notModifiedResponseIncludesExpiresHeader() throws IOException, ServletException, |
| ParseException |
| { |
| try |
| { |
| application = new MockApplication(); |
| WicketFilter filter = new WicketFilter(); |
| filter.init(new FilterTestingConfig()); |
| ThreadContext.setApplication(application); |
| DynamicImageResource resource = new DynamicImageResource() |
| { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected byte[] getImageData(Attributes attributes) |
| { |
| throw new UnsupportedOperationException("Not implemented"); |
| } |
| |
| @Override |
| protected ResourceResponse newResourceResponse(Attributes attributes) |
| { |
| ResourceResponse response = super.newResourceResponse(attributes); |
| response.setCacheDurationToMaximum(); |
| return response; |
| } |
| }; |
| application.getSharedResources().add("foo.gif", resource); |
| MockHttpServletRequest request = new MockHttpServletRequest(application, null, null); |
| request.setURL(request.getContextPath() + request.getServletPath() + |
| "/wicket/resource/" + Application.class.getName() + "/foo.gif"); |
| setIfModifiedSinceToNextWeek(request); |
| MockHttpServletResponse response = new MockHttpServletResponse(request); |
| filter.doFilter(request, response, new FilterChain() |
| { |
| @Override |
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) |
| throws IOException, ServletException |
| { |
| } |
| }); |
| assertEquals(HttpServletResponse.SC_NOT_MODIFIED, response.getStatus()); |
| String responseExpiresHeader = response.getHeader("Expires"); |
| assertNotNull("Expires header must be set on not modified response", |
| responseExpiresHeader); |
| |
| Date responseExpires = headerDateFormat.parse(responseExpiresHeader); |
| assertTrue("Expected later than current date but was " + responseExpires, |
| responseExpires.after(new Date())); |
| } |
| finally |
| { |
| ThreadContext.detach(); |
| } |
| } |
| |
| @Test |
| public void options() throws IOException, ServletException, ParseException |
| { |
| try |
| { |
| application = new MockApplication(); |
| WicketFilter filter = new WicketFilter(); |
| filter.init(new FilterTestingConfig()); |
| ThreadContext.setApplication(application); |
| final String failure = "Should never get here when an OPTIONS request is issued"; |
| IResource resource = new AbstractResource() |
| { |
| @Override |
| protected ResourceResponse newResourceResponse(Attributes attributes) |
| { |
| |
| fail(failure); |
| return null; |
| } |
| }; |
| application.getSharedResources().add("foo.txt", resource); |
| |
| // check OPTIONS request is processed correctly |
| |
| MockHttpServletRequest request = new MockHttpServletRequest(application, null, null); |
| request.setURL(request.getContextPath() + request.getServletPath() + |
| "/wicket/resource/" + Application.class.getName() + "/foo.txt"); |
| request.setMethod("OPtioNS"); // test that we do not care about case |
| MockHttpServletResponse response = new MockHttpServletResponse(request); |
| filter.doFilter(request, response, new FilterChain() |
| { |
| @Override |
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) |
| throws IOException, ServletException |
| { |
| } |
| }); |
| |
| assertEquals(HttpServletResponse.SC_OK, response.getStatus()); |
| assertEquals("0", response.getHeader("Content-Length")); |
| assertFalse(Strings.isEmpty(response.getHeader("Allow"))); |
| assertTrue(response.getHeader("Allow").toUpperCase().contains("GET")); |
| assertTrue(response.getHeader("Allow").toUpperCase().contains("POST")); |
| |
| // try with a GET request to make sure we fail correctly |
| |
| request = new MockHttpServletRequest(application, null, null); |
| request.setURL(request.getContextPath() + request.getServletPath() + |
| "/wicket/resource/" + Application.class.getName() + "/foo.txt"); |
| response = new MockHttpServletResponse(request); |
| try |
| { |
| filter.doFilter(request, response, new FilterChain() |
| { |
| @Override |
| public void doFilter(ServletRequest servletRequest, |
| ServletResponse servletResponse) throws IOException, ServletException |
| { |
| } |
| }); |
| } |
| catch (AssertionError e) |
| { |
| assertTrue(failure.equals(e.getMessage())); |
| } |
| |
| } |
| finally |
| { |
| ThreadContext.detach(); |
| } |
| } |
| |
| |
| private void setIfModifiedSinceToNextWeek(MockHttpServletRequest request) |
| { |
| Calendar nextWeek = Calendar.getInstance(); |
| nextWeek.add(Calendar.DATE, 7); |
| nextWeek.setTimeZone(TimeZone.getTimeZone("GMT")); |
| request.addDateHeader("If-Modified-Since", nextWeek.getTimeInMillis()); |
| } |
| |
| private String getFilterPath(String filterName, InputStream in) |
| { |
| try |
| { |
| return new WebXmlFile().getUniqueFilterPath(false, filterName, in); |
| } |
| catch (ParserConfigurationException | IOException | SAXException ex) |
| { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| private static class FilterTestingConfig implements FilterConfig |
| { |
| private final Map<String, String> initParameters = new HashMap<>(); |
| |
| public FilterTestingConfig() |
| { |
| initParameters.put(WicketFilter.APP_FACT_PARAM, |
| FilterTestingApplicationFactory.class.getName()); |
| initParameters.put(WicketFilter.FILTER_MAPPING_PARAM, "/servlet/*"); |
| initParameters.put(ContextParamWebApplicationFactory.APP_CLASS_PARAM, |
| MockApplication.class.getName()); |
| initParameters.put(WicketFilter.IGNORE_PATHS_PARAM, "/css,/js,images"); |
| } |
| |
| @Override |
| public String getFilterName() |
| { |
| return getClass().getName(); |
| } |
| |
| @Override |
| public ServletContext getServletContext() |
| { |
| return new MockServletContext(null, null); |
| } |
| |
| @Override |
| public String getInitParameter(String s) |
| { |
| return initParameters.get(s); |
| } |
| |
| @Override |
| public Enumeration<String> getInitParameterNames() |
| { |
| throw new UnsupportedOperationException("Not implemented"); |
| } |
| } |
| |
| /** |
| */ |
| public static class FilterTestingApplicationFactory implements IWebApplicationFactory |
| { |
| @Override |
| public WebApplication createApplication(WicketFilter filter) |
| { |
| return application; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void destroy(WicketFilter filter) |
| { |
| } |
| } |
| |
| /** |
| * testCheckRedirect_1() |
| */ |
| @Test |
| public void checkRedirect_1() |
| { |
| WicketFilter filter = new WicketFilter(); |
| |
| // Simulate url-pattern = "/*" and request = http://localhost:8080 => null == no redirect |
| filter.setFilterPath(""); |
| assertNull("", filter.checkIfRedirectRequired("/", "")); |
| } |
| |
| private static class CheckRedirectWorker implements Runnable |
| { |
| private final WicketFilter filter; |
| private final CountDownLatch startLatch; |
| private final CountDownLatch finishLatch; |
| private final AtomicInteger successCount; |
| |
| public CheckRedirectWorker(WicketFilter filter, CountDownLatch startLatch, |
| CountDownLatch finishLatch, AtomicInteger successCount) |
| { |
| this.filter = filter; |
| this.startLatch = startLatch; |
| this.finishLatch = finishLatch; |
| this.successCount = successCount; |
| } |
| |
| @Override |
| public void run() |
| { |
| try |
| { |
| try |
| { |
| startLatch.await(2, TimeUnit.SECONDS); |
| } |
| catch (InterruptedException e) |
| { |
| fail(); |
| } |
| assertEquals("/filter/", filter.checkIfRedirectRequired("/filter", "")); |
| successCount.incrementAndGet(); |
| } |
| finally |
| { |
| finishLatch.countDown(); |
| } |
| } |
| } |
| |
| /** |
| * Starts {@code threadCount} threads which try to check whether a redirect is required and |
| * initialize {@link WicketFilter#filterPathLength} |
| * |
| * @param threadCount |
| * the number of simultaneous threads |
| */ |
| private void parallelCheckRedirect(int threadCount) |
| { |
| WicketFilter filter = new WicketFilter(); |
| filter.setFilterPath("filter/"); |
| AtomicInteger successCount = new AtomicInteger(0); |
| CountDownLatch startLatch = new CountDownLatch(1); |
| CountDownLatch finishLatch = new CountDownLatch(threadCount); |
| for (int i = 0; i < threadCount; i++) |
| { |
| new Thread(new CheckRedirectWorker(filter, startLatch, finishLatch, successCount)).start(); |
| } |
| startLatch.countDown(); |
| try |
| { |
| finishLatch.await(2, TimeUnit.SECONDS); |
| } |
| catch (InterruptedException e) |
| { |
| fail(); |
| } |
| assertEquals("all threads finished", 0, finishLatch.getCount()); |
| assertEquals("all redirects correct", threadCount, successCount.get()); |
| } |
| |
| /** |
| * <a href="https://issues.apache.org/jira/browse/WICKET-3544">WICKET-3544</a> |
| * <p> |
| * Runs 1000 times 8 simultaneous threads which try to initialize WicketFilter#filterPathLength |
| */ |
| @Test |
| public void repeatedParallelCheckRedirect() |
| { |
| int threadCount = 8; |
| int repeatCount = 1000; |
| for (int i = 0; i < repeatCount; i++) |
| { |
| parallelCheckRedirect(threadCount); |
| } |
| } |
| |
| /** |
| * <a href="https://issues.apache.org/jira/browse/WICKET-3750">WICKET-3750</a> |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void ignorePaths() throws Exception |
| { |
| application = spy(new MockApplication()); |
| WicketFilter filter = new WicketFilter(); |
| filter.init(new FilterTestingConfig()); |
| |
| HttpServletRequest request = mock(HttpServletRequest.class); |
| when(request.getLocale()).thenReturn(new Locale("bg", "BG")); |
| when(request.getRequestURI()).thenReturn("/contextPath/js/bla.js") |
| .thenReturn("/contextPath/css/bla.css") |
| .thenReturn("/contextPath/images/bla.img") |
| .thenReturn("/contextPath/servlet/wicket/bookmarkable/" + DummyHomePage.class.getName()); |
| when(request.getContextPath()).thenReturn("/contextPath"); |
| when(request.getMethod()).thenReturn("POST"); |
| HttpServletResponse response = mock(HttpServletResponse.class); |
| when(response.encodeRedirectURL(Matchers.anyString())).thenAnswer(new Answer<String>() |
| { |
| @Override |
| public String answer(InvocationOnMock invocation) throws Throwable |
| { |
| return (String)invocation.getArguments()[0]; |
| } |
| }); |
| FilterChain chain = mock(FilterChain.class); |
| |
| // execute 3 requests - 1 for bla.js, 1 for bla.css and 1 for bla.img |
| for (int i = 0; i < 3; i++) |
| { |
| boolean isProcessed = filter.processRequest(request, response, chain); |
| assertFalse(isProcessed); |
| verify(application, Mockito.never()).newWebRequest(Matchers.eq(request), |
| Matchers.anyString()); |
| verify(application, Mockito.never()).newWebResponse(Matchers.any(WebRequest.class), |
| Matchers.eq(response)); |
| verify(chain, Mockito.times(i + 1)).doFilter(request, response); |
| } |
| |
| // execute the request to /something/real |
| boolean isProcessed = filter.processRequest(request, response, chain); |
| assertTrue(isProcessed); |
| verify(application).newWebRequest(Matchers.eq(request), Matchers.anyString()); |
| verify(application).newWebResponse(Matchers.any(WebRequest.class), Matchers.eq(response)); |
| // the request is processed so the chain is not executed |
| verify(chain, Mockito.times(3)).doFilter(request, response); |
| } |
| |
| /** |
| * <a href="https://issues.apache.org/jira/browse/WICKET-4626">WICKET-4626</a> |
| * <p> |
| * Test method WicketFilter#canonicaliseFilterPath(String) |
| * </p> |
| */ |
| @Test |
| public void canonicaliseFilterPath() |
| { |
| String s; |
| |
| s = WicketFilter.canonicaliseFilterPath(""); |
| assertEquals("", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("/"); |
| assertEquals("", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("//"); |
| assertEquals("", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("///"); |
| assertEquals("", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("/wicket"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("/wicket/"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("wicket/"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("wicket"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("///wicket"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("///wicket///"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("wicket///"); |
| assertEquals("wicket/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("/wicket/foobar"); |
| assertEquals("wicket/foobar/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("/wicket/foobar/"); |
| assertEquals("wicket/foobar/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("wicket/foobar/"); |
| assertEquals("wicket/foobar/", s); |
| |
| s = WicketFilter.canonicaliseFilterPath("/wicket///foobar/"); |
| assertEquals("wicket///foobar/", s); // ok we're not perfect! |
| } |
| |
| } |