|
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