/******************************************************************************* * Copyright (c) 2010, 2015 EclipseSource and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ package org.eclipse.rap.rwt.engine; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.CONNECTION_ID; import static org.junit.Assert.*; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.eclipse.rap.rwt.client.Client; import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl; import org.eclipse.rap.rwt.internal.client.ClientSelector; import org.eclipse.rap.rwt.internal.lifecycle.EntryPointManager; import org.eclipse.rap.rwt.internal.service.ContextProvider; import org.eclipse.rap.rwt.internal.service.ServiceManagerImpl; import org.eclipse.rap.rwt.internal.service.ServiceStore; import org.eclipse.rap.rwt.internal.service.StartupPage; import org.eclipse.rap.rwt.internal.service.UISessionImpl; import org.eclipse.rap.rwt.internal.textsize.ProbeStore; import org.eclipse.rap.rwt.internal.textsize.TextSizeStorage; import org.eclipse.rap.rwt.internal.theme.Theme; import org.eclipse.rap.rwt.internal.theme.ThemeManager; import org.eclipse.rap.rwt.internal.util.HTTP; import org.eclipse.rap.rwt.service.ApplicationContext; import org.eclipse.rap.rwt.service.ServiceHandler; import org.eclipse.rap.rwt.service.UISession; import org.eclipse.rap.rwt.testfixture.internal.TestHttpSession; import org.eclipse.rap.rwt.testfixture.internal.TestRequest; import org.eclipse.rap.rwt.testfixture.internal.TestResponse; import org.junit.After; import org.junit.Before; import org.junit.Test; public class RWTServlet_Test { private ApplicationContextImpl applicationContext; private StartupPage startupPage; private RWTServlet servlet; private TestRequest request; private TestResponse response; @Before public void setUp() throws ServletException { startupPage = mock( StartupPage.class ); applicationContext = mockApplicationContext( startupPage ); servlet = new RWTServlet() { @Override public ServletContext getServletContext() { return mockServletContext( applicationContext ); } }; servlet.init(); request = new TestRequest(); request.setSession( new TestHttpSession() ); response = new TestResponse(); } @After public void tearDown() { ContextProvider.disposeContext(); } @Test public void testHandleRequest_whenApplicationContextNotReady() throws Exception { fakeAllowsRequest( applicationContext, false ); HttpServletResponse response = mock( HttpServletResponse.class ); servlet.doGet( request, response ); verify( response ).sendError( SC_SERVICE_UNAVAILABLE ); verifyNoMoreInteractions( response ); } @Test public void testHandleRequest_withValidUrl() throws Exception { ServiceHandler lifeCycleServiceHandler = mock( ServiceHandler.class ); fakeServiceHandler( applicationContext, lifeCycleServiceHandler ); request.setMethod( HTTP.METHOD_POST ); request.setContentType( HTTP.CONTENT_TYPE_JSON ); request.setServletPath( "/foo" ); request.setPathInfo( null ); request.setSession( mock( HttpSession.class ) ); servlet.doGet( request, response ); verify( lifeCycleServiceHandler ).service( request, response ); } @Test public void testHandleRequest_withRootServlet() throws Exception { ServiceHandler lifeCycleServiceHandler = mock( ServiceHandler.class ); fakeServiceHandler( applicationContext, lifeCycleServiceHandler ); request.setMethod( HTTP.METHOD_POST ); request.setContentType( HTTP.CONTENT_TYPE_JSON ); request.setServletPath( "" ); request.setPathInfo( "/" ); request.setSession( mock( HttpSession.class ) ); servlet.doGet( request, response ); verify( lifeCycleServiceHandler ).service( request, response ); } @Test public void testHandleRequest_withTrailingSlash() throws Exception { request.setServletPath( "/foo" ); request.setPathInfo( "/" ); servlet.doGet( request, response ); assertEquals( SC_NOT_FOUND, response.getErrorStatus() ); } @Test public void testHandleRequest_withIllegalPathInfo() throws Exception { request.setServletPath( "/foo" ); request.setPathInfo( "bar" ); servlet.doGet( request, response ); assertEquals( SC_NOT_FOUND, response.getErrorStatus() ); } @Test public void testHandleRequest_toCustomServiceHandler_hasUISession() throws Exception { UISessionImpl uiSession = new UISessionImpl( applicationContext, request.getSession(), "cid" ); uiSession.attachToHttpSession(); final AtomicReference<UISession> uiSessionRef = new AtomicReference<>(); fakeServiceHandler( applicationContext, new ServiceHandler() { public void service( HttpServletRequest request, HttpServletResponse response) { uiSessionRef.set( ContextProvider.getUISession() ); } } ); request.setParameter( ServiceManagerImpl.REQUEST_PARAM, "foo" ); request.setParameter( CONNECTION_ID, "cid" ); servlet.doGet( request, response ); assertSame( uiSession, uiSessionRef.get() ); } @Test public void testHandleRequest_toCustomServiceHandler_doesNotCreateNewUISession() throws Exception { final AtomicReference<UISession> uiSessionRef = new AtomicReference<>(); fakeServiceHandler( applicationContext, new ServiceHandler() { public void service( HttpServletRequest request, HttpServletResponse response) { uiSessionRef.set( ContextProvider.getUISession() ); } } ); request.setParameter( ServiceManagerImpl.REQUEST_PARAM, "foo" ); servlet.doGet( request, response ); assertNull( uiSessionRef.get() ); } @Test public void testServiceHandlerHasServiceStore() throws ServletException, IOException { final AtomicReference<ServiceStore> serviceStoreRef = new AtomicReference<>(); fakeServiceHandler( applicationContext, new ServiceHandler() { public void service( HttpServletRequest request, HttpServletResponse response) { serviceStoreRef.set( ContextProvider.getServiceStore() ); } } ); request.setParameter( ServiceManagerImpl.REQUEST_PARAM, "foo" ); request.setSession( new TestHttpSession() ); servlet.doPost( request, new TestResponse() ); assertNotNull( serviceStoreRef.get() ); } @Test public void testSetApplicationContextInServiceContext() throws ServletException, IOException { final AtomicReference<ApplicationContext> appContextRef = new AtomicReference<>(); fakeServiceHandler( applicationContext, new ServiceHandler() { public void service( HttpServletRequest request, HttpServletResponse response) { appContextRef.set( ContextProvider.getContext().getApplicationContext() ); } } ); request.setParameter( ServiceManagerImpl.REQUEST_PARAM, "foo" ); request.setSession( new TestHttpSession() ); servlet.doPost( request, new TestResponse() ); assertSame( applicationContext, appContextRef.get() ); } @Test public void testStartupContent_withHtmlAcceptHeader() throws Exception { request.setMethod( HTTP.METHOD_GET ); request.setHeader( HTTP.HEADER_ACCEPT, HTTP.CONTENT_TYPE_HTML ); servlet.service( request, response ); verify( startupPage ).send( response ); } @Test public void testStartupContent_withJsonAcceptHeader() throws Exception { request.setMethod( HTTP.METHOD_GET ); request.setHeader( HTTP.HEADER_ACCEPT, HTTP.CONTENT_TYPE_JSON ); servlet.service( request, response ); verifyZeroInteractions( startupPage ); assertEquals( "application/json; charset=UTF-8", response.getHeader( "Content-Type" ) ); } @Test public void testStartupContent_withoutAcceptHeader() throws Exception { request.setMethod( HTTP.METHOD_GET ); servlet.service( request, response ); verify( startupPage ).send( response ); } @Test public void testStartupPage_forHeadRequest() throws Exception { request.setMethod( "HEAD" ); servlet.service( request, response ); verify( startupPage ).send( any( HttpServletResponse.class ) ); } @Test public void testHandlesInvalidRequestContentType() throws Exception { // SECURITY: Checking the content-type prevents CSRF attacks, see bug 413668 // Also allows application to be started with POST request, see bug 416445 request.setParameter( CONNECTION_ID, "cid" ); request.setMethod( HTTP.METHOD_POST ); request.setContentType( "text/plain" ); servlet.service( request, response ); verify( startupPage ).send( response ); } private static ServletContext mockServletContext( ApplicationContext applicationContext ) { ServletContext servletContext = mock( ServletContext.class ); when( servletContext.getAttribute( anyString() ) ).thenReturn( applicationContext ); return servletContext; } private static ApplicationContextImpl mockApplicationContext( StartupPage startupPage ) { ApplicationContextImpl applicationContext = mock( ApplicationContextImpl.class ); when( applicationContext.getEntryPointManager() ).thenReturn( mock( EntryPointManager.class ) ); ThemeManager themeManager = createThemeManager(); when( applicationContext.getThemeManager() ).thenReturn( themeManager ); when( applicationContext.getStartupPage() ).thenReturn( startupPage ); ClientSelector clientSelector = createClientSelector(); when( applicationContext.getClientSelector() ).thenReturn( clientSelector ); when( applicationContext.getProbeStore() ).thenReturn( createProbeStore() ); when( Boolean.valueOf( applicationContext.isActive() ) ).thenReturn( Boolean.TRUE ); when( Boolean.valueOf( applicationContext.allowsRequests() ) ).thenReturn( Boolean.TRUE ); return applicationContext; } private static ProbeStore createProbeStore() { return new ProbeStore( new TextSizeStorage() ); } private static ClientSelector createClientSelector() { Client client = mock( Client.class ); ClientSelector clientSelector = mock( ClientSelector.class ); when( clientSelector.getSelectedClient( any( UISession.class ) ) ).thenReturn( client ); return clientSelector; } private static ThemeManager createThemeManager() { ThemeManager themeManager = mock( ThemeManager.class ); when( themeManager.getTheme( any( String.class) ) ).thenReturn( mock( Theme.class ) ); return themeManager; } private static void fakeServiceHandler( ApplicationContext applicationContext, ServiceHandler serviceHandler ) { ServiceManagerImpl serviceManager = mock( ServiceManagerImpl.class ); when( serviceManager.getHandler() ).thenReturn( serviceHandler ); when( applicationContext.getServiceManager() ).thenReturn( serviceManager ); } private static void fakeAllowsRequest( ApplicationContextImpl applicationContext, boolean allowsRequests ) { when( Boolean.valueOf( applicationContext.allowsRequests() ) ) .thenReturn( Boolean.valueOf( allowsRequests ) ); } }