mirror of https://github.com/halo-dev/halo
Fix the problem of crashing requests when slug names of single page contains special chars (#4013)
#### What type of PR is this? /kind bug /area core /milestone 2.6.x #### What this PR does / why we need it: This PR refactors request predicate of path when building router functions for single page. I only compare the exact slug name instead of treating it as a URI template. See <https://github.com/halo-dev/halo/issues/3931> for more. #### Which issue(s) this PR fixes: Fixes <https://github.com/halo-dev/halo/issues/3931> #### Special notes for your reviewer: 1. Try to create a single page with slug name like `{}[]{[]}[{}]`. 2. Publish the single page. 3. Try to request the page. 4. See the result. #### Does this PR introduce a user-facing change? ```release-note 修复页面别名包含特殊字符导致无法访问的问题 ```pull/4014/head^2
parent
ee1ea06171
commit
4c2e8410b9
|
@ -1,24 +1,29 @@
|
|||
package run.halo.app.theme.router;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.methods;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicate;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
|
@ -41,7 +46,7 @@ import run.halo.app.theme.finders.SinglePageFinder;
|
|||
@RequiredArgsConstructor
|
||||
public class SinglePageRoute
|
||||
implements RouterFunction<ServerResponse>, Reconciler<Reconciler.Request>, DisposableBean {
|
||||
private final Map<NameSlugPair, HandlerFunction<ServerResponse>> quickRouteMap =
|
||||
private Map<NameSlugPair, HandlerFunction<ServerResponse>> quickRouteMap =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
@ -58,6 +63,15 @@ public class SinglePageRoute
|
|||
.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set quickRouteMap. This method is only for testing.
|
||||
*
|
||||
* @param quickRouteMap fresh quickRouteMap.
|
||||
*/
|
||||
void setQuickRouteMap(Map<NameSlugPair, HandlerFunction<ServerResponse>> quickRouteMap) {
|
||||
this.quickRouteMap = quickRouteMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NonNull RouterFunctions.Visitor visitor) {
|
||||
routerFunctions().forEach(routerFunction -> routerFunction.accept(visitor));
|
||||
|
@ -66,14 +80,23 @@ public class SinglePageRoute
|
|||
private List<RouterFunction<ServerResponse>> routerFunctions() {
|
||||
return quickRouteMap.keySet().stream()
|
||||
.map(nameSlugPair -> {
|
||||
String routePath = singlePageRoute(nameSlugPair.slug());
|
||||
return RouterFunctions.route(GET(routePath)
|
||||
var routePath = singlePageRoute(nameSlugPair.slug());
|
||||
return RouterFunctions.route(methods(HttpMethod.GET)
|
||||
.and(exactPath(routePath))
|
||||
.and(RequestPredicates.accept(MediaType.TEXT_HTML)),
|
||||
handlerFunction(nameSlugPair.name()));
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private RequestPredicate exactPath(String path) {
|
||||
return request -> {
|
||||
var encodedRoutePath = UriUtils.encodePath(path, StandardCharsets.UTF_8);
|
||||
var requestPath = request.requestPath().pathWithinApplication().value();
|
||||
return Objects.equals(requestPath, encodedRoutePath);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result reconcile(Request request) {
|
||||
client.fetch(SinglePage.class, request.name())
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
package run.halo.app.theme.router;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||
|
@ -20,13 +27,16 @@ import org.springframework.web.reactive.function.server.RouterFunctions;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.theme.DefaultTemplateEnum;
|
||||
import run.halo.app.theme.finders.SinglePageFinder;
|
||||
import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||
import run.halo.app.theme.router.SinglePageRoute.NameSlugPair;
|
||||
|
||||
/**
|
||||
* Tests for {@link SinglePageRoute}.
|
||||
|
@ -97,4 +107,27 @@ class SinglePageRouteTest {
|
|||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowErrorIfSlugNameContainsSpecialChars() {
|
||||
var specialChars = "/with-special-chars-{}-[]-{{}}-{[]}-[{}]";
|
||||
var specialCharsUri =
|
||||
URI.create(UriUtils.encodePath(specialChars, UTF_8));
|
||||
var mockHttpRequest = MockServerHttpRequest.get(specialCharsUri.toString())
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.build();
|
||||
var mockExchange = MockServerWebExchange.from(mockHttpRequest);
|
||||
var request = MockServerRequest.builder()
|
||||
.exchange(mockExchange)
|
||||
.uri(specialCharsUri)
|
||||
.method(HttpMethod.GET)
|
||||
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE)
|
||||
.build();
|
||||
var nameSlugPair = new NameSlugPair("fake-single-page", specialChars);
|
||||
singlePageRoute.setQuickRouteMap(Map.of(nameSlugPair, r -> ServerResponse.ok().build()));
|
||||
StepVerifier.create(singlePageRoute.route(request))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue