提交 d83fb099 编写于 作者: A Arjen Poutsma

Change header encoding to UTF8 in DefaultPartHttpMessageReader

This commit changes the encoding used to parse multipart headers from
ISO-8859-1 to UTF-8, in accordance with RFC 7578.

Closes gh-26736
上级 b651c10e
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -17,6 +17,7 @@
package org.springframework.http.codec.multipart;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
......@@ -78,6 +79,8 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
private Mono<Path> fileStorageDirectory = Mono.defer(this::defaultFileStorageDirectory).cache();
private Charset headersCharset = StandardCharsets.UTF_8;
/**
* Configure the maximum amount of memory that is allowed per headers section of each part.
......@@ -188,6 +191,18 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
this.streaming = streaming;
}
/**
* Sets the character set used to decode headers. Defaults to
* UTF-8 as per RFC 7578.
* @param headersCharset the charset to use for decoding headers
* @since 5.3.6
* @see <a href="https://tools.ietf.org/html/rfc7578#section-5.1">RFC-7578 Section 5.2</a>
*/
public void setHeadersCharset(Charset headersCharset) {
Assert.notNull(headersCharset, "HeadersCharset must not be null");
this.headersCharset = headersCharset;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.singletonList(MediaType.MULTIPART_FORM_DATA);
......@@ -214,7 +229,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
message.getHeaders().getContentType() + "\""));
}
Flux<MultipartParser.Token> tokens = MultipartParser.parse(message.getBody(), boundary,
this.maxHeadersSize);
this.maxHeadersSize, this.headersCharset);
return PartGenerator.createParts(tokens, this.maxParts, this.maxInMemorySize, this.maxDiskUsagePerPart,
this.streaming, this.fileStorageDirectory, this.blockingOperationScheduler);
......@@ -222,7 +237,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
}
@Nullable
private static byte[] boundary(HttpMessage message) {
private byte[] boundary(HttpMessage message) {
MediaType contentType = message.getHeaders().getContentType();
if (contentType != null) {
String boundary = contentType.getParameter("boundary");
......@@ -231,7 +246,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
if (len > 2 && boundary.charAt(0) == '"' && boundary.charAt(len - 1) == '"') {
boundary = boundary.substring(1, len - 1);
}
return boundary.getBytes(StandardCharsets.ISO_8859_1);
return boundary.getBytes(this.headersCharset);
}
}
return null;
......
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -16,7 +16,7 @@
package org.springframework.http.codec.multipart;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
......@@ -69,11 +69,14 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
private final AtomicBoolean requestOutstanding = new AtomicBoolean();
private final Charset headersCharset;
private MultipartParser(FluxSink<Token> sink, byte[] boundary, int maxHeadersSize) {
private MultipartParser(FluxSink<Token> sink, byte[] boundary, int maxHeadersSize, Charset headersCharset) {
this.sink = sink;
this.boundary = boundary;
this.maxHeadersSize = maxHeadersSize;
this.headersCharset = headersCharset;
this.state = new AtomicReference<>(new PreambleState());
}
......@@ -82,11 +85,13 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
* @param buffers the input buffers
* @param boundary the multipart boundary, as found in the {@code Content-Type} header
* @param maxHeadersSize the maximum buffered header size
* @param headersCharset the charset to use for decoding headers
* @return a stream of parsed tokens
*/
public static Flux<Token> parse(Flux<DataBuffer> buffers, byte[] boundary, int maxHeadersSize) {
public static Flux<Token> parse(Flux<DataBuffer> buffers, byte[] boundary, int maxHeadersSize,
Charset headersCharset) {
return Flux.create(sink -> {
MultipartParser parser = new MultipartParser(sink, boundary, maxHeadersSize);
MultipartParser parser = new MultipartParser(sink, boundary, maxHeadersSize, headersCharset);
sink.onCancel(parser::onSinkCancel);
sink.onRequest(n -> parser.requestBuffer());
buffers.subscribe(parser);
......@@ -180,7 +185,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
/**
* Represents the output of {@link #parse(Flux, byte[], int)}.
* Represents the output of {@link #parse(Flux, byte[], int, Charset)}.
*/
public abstract static class Token {
......@@ -372,7 +377,6 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
DataBufferUtils.release(buf);
emitHeaders(parseHeaders());
// TODO: no need to check result of changeState, no further statements
changeState(this, new BodyState(), bodyBuf);
}
else {
......@@ -408,7 +412,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
}
DataBuffer joined = this.buffers.get(0).factory().join(this.buffers);
this.buffers.clear();
String string = joined.toString(StandardCharsets.ISO_8859_1);
String string = joined.toString(MultipartParser.this.headersCharset);
DataBufferUtils.release(joined);
String[] lines = string.split(HEADER_ENTRY_SEPARATOR);
HttpHeaders result = new HttpHeaders();
......
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
......@@ -251,6 +252,24 @@ public class DefaultPartHttpMessageReaderTests {
latch.await();
}
@ParameterizedDefaultPartHttpMessageReaderTest
public void utf8Headers(String displayName, DefaultPartHttpMessageReader reader) throws InterruptedException {
MockServerHttpRequest request = createRequest(
new ClassPathResource("utf8.multipart", getClass()), "\"simple-boundary\"");
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
CountDownLatch latch = new CountDownLatch(1);
StepVerifier.create(result)
.consumeNextWith(part -> {
assertThat(part.headers()).containsEntry("Føø", Collections.singletonList("Bår"));
testPart(part, null, "This is plain ASCII text.", latch);
})
.verifyComplete();
latch.await();
}
private void testBrowser(DefaultPartHttpMessageReader reader, Resource resource, String boundary)
throws InterruptedException {
......
--simple-boundary
Føø: Bår
This is plain ASCII text.
--simple-boundary--
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册