/** * Handle an exception in the servlet. * * <p>This routine should be called whenever an exception has surfaced to the top level of the * servlet. It should not be overridden unless Aura is entirely subsumed. Most special cases can * be handled by the Aura user by implementing {@link ExceptionAdapter ExceptionAdapter}. * * @param t the throwable to write out. * @param quickfix is this exception a valid quick-fix * @param context the aura context. * @param request the request. * @param response the response. * @param written true if we have started writing to the output stream. * @throws IOException if the output stream does. * @throws ServletException if send404 does (should not generally happen). */ @Override public void handleServletException( Throwable t, boolean quickfix, AuraContext context, HttpServletRequest request, HttpServletResponse response, boolean written) throws IOException { try { Throwable mappedEx = t; boolean map = !quickfix; Format format = context.getFormat(); // // This seems to fail, though the documentation implies that you can do // it. // // if (written && !response.isCommitted()) { // response.resetBuffer(); // written = false; // } if (!written) { // Should we only delete for JSON? setNoCache(response); } if (mappedEx instanceof IOException) { // // Just re-throw IOExceptions. // throw (IOException) mappedEx; } else if (mappedEx instanceof NoAccessException) { Throwable cause = mappedEx.getCause(); String denyMessage = mappedEx.getMessage(); map = false; if (cause != null) { // // Note that the exception handler can remap the cause here. // cause = exceptionAdapter.handleException(cause); denyMessage += ": cause = " + cause.getMessage(); } // // Is this correct?!?!?! // if (format != Format.JSON) { this.send404(request.getServletContext(), request, response); if (!isProductionMode(context.getMode())) { // Preserve new lines and tabs in the stacktrace since this is directly being written on // to the // page denyMessage = "<pre>" + AuraTextUtil.escapeForHTML(denyMessage) + "</pre>"; response.getWriter().println(denyMessage); } return; } } else if (mappedEx instanceof QuickFixException) { if (isProductionMode(context.getMode())) { // // In production environments, we want wrap the quick-fix. But be a little careful here. // We should never mark the top level as a quick-fix, because that means that we gack // on every mis-spelled app. In this case we simply send a 404 and bolt. // if (mappedEx instanceof DefinitionNotFoundException) { DefinitionNotFoundException dnfe = (DefinitionNotFoundException) mappedEx; if (dnfe.getDescriptor() != null && dnfe.getDescriptor().equals(context.getApplicationDescriptor())) { // We're in production and tried to hit an aura app that doesn't exist. // just show the standard 404 page. this.send404(request.getServletContext(), request, response); return; } } map = true; mappedEx = new AuraUnhandledException("404 Not Found (Application Error)", mappedEx); } } if (map) { mappedEx = exceptionAdapter.handleException(mappedEx); } PrintWriter out = response.getWriter(); // // If we have written out data, We are kinda toast in this case. // We really want to roll it all back, but we can't, so we opt // for the best we can do. For HTML we can do nothing at all. // if (format == Format.JSON) { if (!written) { out.write(CSRF_PROTECT); } // // If an exception happened while we were emitting JSON, we want the // client to ignore the now-corrupt data structure. 404s and 500s // cause the client to prepend /*, so we can effectively erase the // bad data by appending a */ here and then serializing the exception // info. // out.write("*/"); // // Unfortunately we can't do the following now. It might be possible // in some cases, but we don't want to go there unless we have to. // } if (format == Format.JS || format == Format.CSS) { // Make sure js and css doesn't get cached in browser, appcache, etc response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); } if (format == Format.JSON || format == Format.HTML || format == Format.JS || format == Format.CSS) { // // We only write out exceptions for HTML or JSON. // Seems bogus, but here it is. // // Start out by cleaning out some settings to ensure we don't // check too many things, leading to a circular failure. Note // that this is still a bit dangerous, as we seem to have a lot // of magic in the serializer. // // Clear the InstanceStack before trying to serialize the exception since the Throwable has // likely // rendered the stack inaccurate, and may falsely trigger NoAccessExceptions. InstanceStack stack = this.contextService.getCurrentContext().getInstanceStack(); List<String> list = stack.getStackInfo(); for (int count = list.size(); count > 0; count--) { stack.popInstance(stack.peek()); } serializationService.write(mappedEx, null, out); if (format == Format.JSON) { out.write("/*ERROR*/"); } } } catch (IOException ioe) { throw ioe; } catch (Throwable death) { // // Catch any other exception and log it. This is actually kinda bad, because something has // gone horribly wrong. We should write out some sort of generic page other than a 404, // but at this point, it is unclear what we can do, as stuff is breaking right and left. // try { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); exceptionAdapter.handleException(death); if (!isProductionMode(context.getMode())) { response.getWriter().println(death.getMessage()); } } catch (IOException ioe) { throw ioe; } catch (Throwable doubleDeath) { // we are totally hosed. if (!isProductionMode(context.getMode())) { response.getWriter().println(doubleDeath.getMessage()); } } } finally { this.contextService.endContext(); } }
@Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException { if (!Aura.getConfigAdapter().isTestAllowed()) { chain.doFilter(req, res); return; } TestContextAdapter testContextAdapter = Aura.get(TestContextAdapter.class); if (testContextAdapter == null) { chain.doFilter(req, res); return; } // Check for requests to execute a JSTest, i.e. initial component GETs with particular // parameters. HttpServletRequest request = (HttpServletRequest) req; if ("GET".equals(request.getMethod())) { String contextPath = request.getContextPath(); String uri = request.getRequestURI(); String browserType = request.getParameter("aura.browserType"); if (browserType == null) { // read it from request header String ua = request.getHeader(HttpHeaders.USER_AGENT); if (ua != null) { ua = ua.toLowerCase(); if (ua.contains("chrome")) { browserType = "GOOGLECHROME"; } else if (ua.contains("safari")) { browserType = "SAFARI"; } else if (ua.contains("firefox")) { browserType = "FIREFOX"; } else if (ua.contains("ipad")) { browserType = "IPAD"; } else if (ua.contains("iphone")) { browserType = "IPHONE"; } else if (ua.contains("msie 10")) { browserType = "IE10"; } else if (ua.contains("msie 9")) { browserType = "IE9"; } else if (ua.contains("msie 8")) { browserType = "IE8"; } else if (ua.contains("msie 7")) { browserType = "IE7"; } else if (ua.contains("msie 6")) { browserType = "IE6"; } else if (ua.contains("trident/7.0")) { browserType = "IE11"; } else if (ua.contains("edge/12")) { browserType = "IE12"; } else { browserType = "OTHER"; } } } String path; if (uri.startsWith(contextPath)) { path = uri.substring(contextPath.length()); } else { path = uri; } Matcher matcher = AuraRewriteFilter.DESCRIPTOR_PATTERN.matcher(path); if (matcher.matches()) { // Extract the target component since AuraContext usually does not have the app descriptor // set yet. DefType type = "app".equals(matcher.group(3)) ? DefType.APPLICATION : DefType.COMPONENT; String namespace = matcher.group(1); String name = matcher.group(2); DefDescriptor<?> targetDescriptor = Aura.getDefinitionService() .getDefDescriptor( String.format("%s:%s", namespace, name), type.getPrimaryInterface()); // Check if a single jstest is being requested. String testToRun = jstestToRun.get(request); if (testToRun != null && !testToRun.isEmpty()) { AuraContext context = Aura.getContextService().getCurrentContext(); Format format = context.getFormat(); switch (format) { case HTML: TestCaseDef testDef; TestContext testContext; String targetUri; try { TestSuiteDef suiteDef = getTestSuite(targetDescriptor); testDef = getTestCase(suiteDef, testToRun); testDef.validateDefinition(); testDef.setCurrentBrowser(browserType); testContextAdapter.getTestContext(testDef.getQualifiedName()); testContextAdapter.release(); testContext = testContextAdapter.getTestContext(testDef.getQualifiedName()); targetUri = buildJsTestTargetUri(targetDescriptor, testDef); } catch (QuickFixException e) { ((HttpServletResponse) res).setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); res.setCharacterEncoding(AuraBaseServlet.UTF_ENCODING); res.getWriter().append(e.getMessage()); Aura.getExceptionAdapter().handleException(e); return; } // Load any test mocks. Collection<Definition> mocks = testDef.getLocalDefs(); testContext.getLocalDefs().addAll(mocks); loadTestMocks(context, true, testContext.getLocalDefs()); // Capture the response and inject tags to load jstest. String capturedResponse = captureResponse(req, res, targetUri); if (capturedResponse != null) { res.setCharacterEncoding(AuraBaseServlet.UTF_ENCODING); if (!Aura.getContextService().isEstablished()) { // There was an error in the original response, so just write the response out. res.getWriter().write(capturedResponse); } else { String testTag = buildJsTestScriptTag(targetDescriptor, testToRun, capturedResponse); injectScriptTags(res.getWriter(), capturedResponse, testTag); } return; } case JS: res.setCharacterEncoding(AuraBaseServlet.UTF_ENCODING); writeJsTestScript(res.getWriter(), targetDescriptor, testToRun); return; default: // Pass it on. } } // aurajstest:jstest app is invokable in the following ways: // ?aura.mode=JSTEST - run all tests // ?aura.mode JSTEST&test=XXX - run single test // ?aura.jstest - run all tests // ?aura.jstest=XXX - run single test // ?aura.jstestrun - run all tests // TODO: delete JSTEST mode String jstestAppRequest = jstestAppFlag.get(request); Mode mode = AuraContextFilter.mode.get(request, Mode.PROD); if (mode == Mode.JSTEST || mode == Mode.JSTESTDEBUG || jstestAppRequest != null || testToRun != null) { mode = mode.toString().endsWith("DEBUG") ? Mode.AUTOJSTESTDEBUG : Mode.AUTOJSTEST; String qs = String.format("descriptor=%s:%s&defType=%s", namespace, name, type.name()); String testName = null; if (jstestAppRequest != null && !jstestAppRequest.isEmpty()) { testName = jstestAppRequest; } else if (testToRun != null && !testToRun.isEmpty()) { testName = testToRun; } if (testName != null) { qs = qs + "&test=" + testName; } String newUri = createURI( "aurajstest", "jstest", DefType.APPLICATION, mode, Authentication.AUTHENTICATED.name(), qs); RequestDispatcher dispatcher = servletContext.getContext(newUri).getRequestDispatcher(newUri); if (dispatcher != null) { dispatcher.forward(req, res); return; } } } } // Handle mock definitions specified in the tests. TestContext testContext = getTestContext(request); if (testContext == null) { // During manual testing, the test context adapter may not always get cleared. testContextAdapter.clear(); } else { ContextService contextService = Aura.getContextService(); if (!contextService.isEstablished()) { LOG.error("Aura context is not established! New context will NOT be created."); chain.doFilter(req, res); return; } AuraContext context = contextService.getCurrentContext(); // Reset mocks if requested, or for the initial GET. boolean doResetMocks = testReset.get(request, Format.HTML.equals(context.getFormat())); loadTestMocks(context, doResetMocks, testContext.getLocalDefs()); } chain.doFilter(req, res); }