Skip to content

Commit

Permalink
Merge pull request #98 from cryptomator/feature/simplify-vue-history-…
Browse files Browse the repository at this point in the history
…mode-filter

Simplify "history mode filter"
  • Loading branch information
overheadhunter authored Jul 29, 2022
2 parents df0e644 + 5f33487 commit 57e1bb0
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.cryptomator.hub;
package org.cryptomator.hub.filters;

import org.eclipse.microprofile.config.inject.ConfigProperty;

Expand All @@ -11,7 +11,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Pattern;

/**
* A global http filter which redirects all 404-responses to the frontend app root. Necessary for using <a href="https://v3.router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations">history mode in the vue router</a>
Expand All @@ -21,8 +20,6 @@
@WebFilter(urlPatterns = "/*")
public class VueHistoryModeFilter extends HttpFilter {

private static final Pattern FILE_NAME_PATTERN = Pattern.compile(".*[.][a-zA-Z\\d]+");

@ConfigProperty(name = "quarkus.resteasy.path")
String apiPathPrefix;

Expand All @@ -31,19 +28,14 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
HttpServletResponse response = (HttpServletResponse) res;
chain.doFilter(request, response);

if (response.getStatus() == 404) {
String path = request.getRequestURI().substring(
request.getContextPath().length()).replaceAll("[/]+$", ""); //delete all "/" at end of string
if (!path.startsWith(apiPathPrefix) && !FILE_NAME_PATTERN.matcher(path).matches()) { //TODO: possibly exclude even more prefixes (e.g. keycloak)
// We could not find the resource, i.e. it is not anything known to the server (i.e. it is not a REST
// endpoint or a servlet), and does not look like a file so try handling it in the front-end routes
// and reset the response status code to 200.
try {
response.setStatus(200);
request.getRequestDispatcher("/").forward(request, response);
} finally {
response.getOutputStream().close();
}
// exclude requests to the ReST API from filtering:
String contextRelativePath = request.getRequestURI().substring(request.getContextPath().length());
if (response.getStatus() == 404 && !contextRelativePath.startsWith(apiPathPrefix)) {
try {
response.setStatus(200);
request.getRequestDispatcher("/").forward(request, response);
} finally {
response.getOutputStream().close();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.cryptomator.hub.filters;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;

import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class VueHistoryModeFilterTest {

private HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
private HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
private FilterChain chain = Mockito.mock(FilterChain.class);

private VueHistoryModeFilter filter = new VueHistoryModeFilter();

@BeforeEach
public void setup() {
filter.apiPathPrefix = "/api";
}

@ParameterizedTest(name = "path = {0}")
@ValueSource(strings = {"/ctx/api", "/ctx/api/foo?k=v", "/ctx/api/foo/bar/", "/ctx/api/"})
@DisplayName("don't filter requests to /ctx/api/*")
public void testDoNotFilterApi(String reqUri) throws ServletException, IOException {
Mockito.doReturn(reqUri).when(req).getRequestURI();
Mockito.doReturn("/ctx").when(req).getContextPath();
Mockito.doReturn(404).when(res).getStatus();

filter.doFilter(req, res, chain);

Mockito.verify(chain).doFilter(req, res);
Mockito.verify(req).getRequestURI();
Mockito.verify(req).getContextPath();
Mockito.verify(res).getStatus();
Mockito.verifyNoMoreInteractions(res, req, chain);
}

@ParameterizedTest(name = "statuscode = {0}")
@ValueSource(ints = {200, 201, 301, 401, 403})
@DisplayName("don't filter non-404 responses")
public void testDoNotFilterNon404(int status) throws ServletException, IOException {
Mockito.doReturn("/ctx/foo").when(req).getRequestURI();
Mockito.doReturn("/ctx").when(req).getContextPath();
Mockito.doReturn(status).when(res).getStatus();

filter.doFilter(req, res, chain);

Mockito.verify(chain).doFilter(req, res);
Mockito.verify(req).getRequestURI();
Mockito.verify(req).getContextPath();
Mockito.verify(res).getStatus();
Mockito.verifyNoMoreInteractions(res, req, chain);
}

@ParameterizedTest(name = "path = {0}")
@ValueSource(strings = {"/ctx", "/ctx/foo?k=v", "/ctx/foo/bar/"})
@DisplayName("filter 404 response to non-api resources")
public void testDoFilterNonApi(String reqUri) throws ServletException, IOException {
var dispatcher = Mockito.mock(RequestDispatcher.class);
var out = Mockito.mock(ServletOutputStream.class);
Mockito.doReturn(reqUri).when(req).getRequestURI();
Mockito.doReturn("/ctx").when(req).getContextPath();
Mockito.doReturn(404).when(res).getStatus();
Mockito.doReturn(dispatcher).when(req).getRequestDispatcher("/");
Mockito.doReturn(out).when(res).getOutputStream();

filter.doFilter(req, res, chain);

Mockito.verify(res).setStatus(200);
Mockito.verify(req).getRequestDispatcher("/");
Mockito.verify(dispatcher).forward(req, res);
Mockito.verify(out).close();
}

}

0 comments on commit 57e1bb0

Please sign in to comment.