|
26 | 26 |
|
27 | 27 | import org.springframework.core.io.buffer.DataBuffer;
|
28 | 28 | import org.springframework.core.io.buffer.DataBufferFactory;
|
| 29 | +import org.springframework.core.io.buffer.DataBufferUtils; |
29 | 30 | import org.springframework.http.HttpStatus;
|
30 | 31 | import org.springframework.http.MediaType;
|
31 | 32 | import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
41 | 42 | * data instead of query string data.
|
42 | 43 | *
|
43 | 44 | * @author Max Batischev
|
| 45 | + * @author Steve Riesenberg |
44 | 46 | * @since 6.5
|
45 | 47 | */
|
46 |
| -public final class ServerFormPostRedirectStrategy implements ServerRedirectStrategy { |
| 48 | +public final class FormPostServerRedirectStrategy implements ServerRedirectStrategy { |
47 | 49 |
|
48 | 50 | private static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
|
49 | 51 |
|
50 |
| - private static final StringKeyGenerator DEFAULT_NONCE_GENERATOR = new Base64StringKeyGenerator( |
51 |
| - Base64.getUrlEncoder().withoutPadding(), 96); |
52 |
| - |
53 | 52 | private static final String REDIRECT_PAGE_TEMPLATE = """
|
54 | 53 | <!DOCTYPE html>
|
55 | 54 | <html lang="en">
|
@@ -79,46 +78,46 @@ public final class ServerFormPostRedirectStrategy implements ServerRedirectStrat
|
79 | 78 | <input name="{{name}}" type="hidden" value="{{value}}" />
|
80 | 79 | """;
|
81 | 80 |
|
| 81 | + private static final StringKeyGenerator DEFAULT_NONCE_GENERATOR = new Base64StringKeyGenerator( |
| 82 | + Base64.getUrlEncoder().withoutPadding(), 96); |
| 83 | + |
82 | 84 | @Override
|
83 | 85 | public Mono<Void> sendRedirect(ServerWebExchange exchange, URI location) {
|
84 |
| - String nonce = DEFAULT_NONCE_GENERATOR.generateKey(); |
85 |
| - String policyDirective = "script-src 'nonce-%s'".formatted(nonce); |
86 |
| - |
87 |
| - ServerHttpResponse response = exchange.getResponse(); |
88 |
| - response.setStatusCode(HttpStatus.OK); |
89 |
| - response.getHeaders().setContentType(MediaType.TEXT_HTML); |
90 |
| - response.getHeaders().add(CONTENT_SECURITY_POLICY_HEADER, policyDirective); |
91 |
| - return response.writeWith(createBuffer(exchange, location, nonce)); |
92 |
| - } |
| 86 | + final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(location); |
93 | 87 |
|
94 |
| - private Mono<DataBuffer> createBuffer(ServerWebExchange exchange, URI location, String nonce) { |
95 |
| - byte[] bytes = createPage(location, nonce); |
96 |
| - DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); |
97 |
| - return Mono.just(bufferFactory.wrap(bytes)); |
98 |
| - } |
99 |
| - |
100 |
| - private byte[] createPage(URI location, String nonce) { |
101 |
| - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(location); |
102 |
| - |
103 |
| - StringBuilder hiddenInputsHtmlBuilder = new StringBuilder(); |
| 88 | + final StringBuilder hiddenInputsHtmlBuilder = new StringBuilder(); |
104 | 89 | for (final Map.Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
|
105 | 90 | final String name = entry.getKey();
|
106 | 91 | for (final String value : entry.getValue()) {
|
107 | 92 | // @formatter:off
|
108 | 93 | final String hiddenInput = HIDDEN_INPUT_TEMPLATE
|
109 |
| - .replace("{{name}}", HtmlUtils.htmlEscape(name)) |
110 |
| - .replace("{{value}}", HtmlUtils.htmlEscape(value)); |
| 94 | + .replace("{{name}}", HtmlUtils.htmlEscape(name)) |
| 95 | + .replace("{{value}}", HtmlUtils.htmlEscape(value)); |
111 | 96 | // @formatter:on
|
112 | 97 | hiddenInputsHtmlBuilder.append(hiddenInput.trim());
|
113 | 98 | }
|
114 | 99 | }
|
| 100 | + |
| 101 | + // Create the script-src policy directive for the Content-Security-Policy header |
| 102 | + final String nonce = DEFAULT_NONCE_GENERATOR.generateKey(); |
| 103 | + final String policyDirective = "script-src 'nonce-%s'".formatted(nonce); |
| 104 | + |
115 | 105 | // @formatter:off
|
116 |
| - return REDIRECT_PAGE_TEMPLATE |
117 |
| - .replace("{{action}}", HtmlUtils.htmlEscape(uriComponentsBuilder.query(null).build().toUriString())) |
118 |
| - .replace("{{params}}", hiddenInputsHtmlBuilder.toString()) |
119 |
| - .replace("{{nonce}}", HtmlUtils.htmlEscape(nonce)) |
120 |
| - .getBytes(StandardCharsets.UTF_8); |
| 106 | + final String html = REDIRECT_PAGE_TEMPLATE |
| 107 | + // Clear the query string as we don't want that to be part of the form action URL |
| 108 | + .replace("{{action}}", HtmlUtils.htmlEscape(uriComponentsBuilder.query(null).build().toUriString())) |
| 109 | + .replace("{{params}}", hiddenInputsHtmlBuilder.toString()) |
| 110 | + .replace("{{nonce}}", HtmlUtils.htmlEscape(nonce)); |
121 | 111 | // @formatter:on
|
| 112 | + |
| 113 | + final ServerHttpResponse response = exchange.getResponse(); |
| 114 | + response.setStatusCode(HttpStatus.OK); |
| 115 | + response.getHeaders().setContentType(MediaType.TEXT_HTML); |
| 116 | + response.getHeaders().set(CONTENT_SECURITY_POLICY_HEADER, policyDirective); |
| 117 | + |
| 118 | + final DataBufferFactory bufferFactory = response.bufferFactory(); |
| 119 | + final DataBuffer buffer = bufferFactory.wrap(html.getBytes(StandardCharsets.UTF_8)); |
| 120 | + return response.writeWith(Mono.just(buffer)).doOnError((error) -> DataBufferUtils.release(buffer)); |
122 | 121 | }
|
123 | 122 |
|
124 | 123 | }
|
0 commit comments