/*
 * Copyright 2002-2020 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.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.http.codec.multipart;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import reactor.core.publisher.Mono;

import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.LoggingCodecSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;

Support class for multipart HTTP message writers.
Author:Rossen Stoyanchev
Since:5.3
/** * Support class for multipart HTTP message writers. * * @author Rossen Stoyanchev * @since 5.3 */
public class MultipartWriterSupport extends LoggingCodecSupport {
THe default charset used by the writer.
/** THe default charset used by the writer. */
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private final List<MediaType> supportedMediaTypes; private Charset charset = DEFAULT_CHARSET;
Constructor with the list of supported media types.
/** * Constructor with the list of supported media types. */
protected MultipartWriterSupport(List<MediaType> supportedMediaTypes) { this.supportedMediaTypes = supportedMediaTypes; }
Return the configured charset for part headers.
/** * Return the configured charset for part headers. */
public Charset getCharset() { return this.charset; }
Set the character set to use for part headers such as "Content-Disposition" (and its filename parameter).

By default this is set to "UTF-8". If changed from this default, the "Content-Type" header will have a "charset" parameter that specifies the character set used.

/** * Set the character set to use for part headers such as * "Content-Disposition" (and its filename parameter). * <p>By default this is set to "UTF-8". If changed from this default, * the "Content-Type" header will have a "charset" parameter that specifies * the character set used. */
public void setCharset(Charset charset) { Assert.notNull(charset, "Charset must not be null"); this.charset = charset; } public List<MediaType> getWritableMediaTypes() { return this.supportedMediaTypes; } public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { if (MultiValueMap.class.isAssignableFrom(elementType.toClass())) { if (mediaType == null) { return true; } for (MediaType supportedMediaType : this.supportedMediaTypes) { if (supportedMediaType.isCompatibleWith(mediaType)) { return true; } } } return false; }
Generate a multipart boundary.

By default delegates to MimeTypeUtils.generateMultipartBoundary().

/** * Generate a multipart boundary. * <p>By default delegates to {@link MimeTypeUtils#generateMultipartBoundary()}. */
protected byte[] generateMultipartBoundary() { return MimeTypeUtils.generateMultipartBoundary(); }
Prepare the MediaType to use by adding "boundary" and "charset" parameters to the given mediaType or "mulitpart/form-data" otherwise by default.
/** * Prepare the {@code MediaType} to use by adding "boundary" and "charset" * parameters to the given {@code mediaType} or "mulitpart/form-data" * otherwise by default. */
protected MediaType getMultipartMediaType(@Nullable MediaType mediaType, byte[] boundary) { Map<String, String> params = new HashMap<>(); if (mediaType != null) { params.putAll(mediaType.getParameters()); } params.put("boundary", new String(boundary, StandardCharsets.US_ASCII)); Charset charset = getCharset(); if (!charset.equals(StandardCharsets.UTF_8) && !charset.equals(StandardCharsets.US_ASCII) ) { params.put("charset", charset.name()); } mediaType = (mediaType != null ? mediaType : MediaType.MULTIPART_FORM_DATA); mediaType = new MediaType(mediaType, params); return mediaType; } protected Mono<DataBuffer> generateBoundaryLine(byte[] boundary, DataBufferFactory bufferFactory) { return Mono.fromCallable(() -> { DataBuffer buffer = bufferFactory.allocateBuffer(boundary.length + 4); buffer.write((byte)'-'); buffer.write((byte)'-'); buffer.write(boundary); buffer.write((byte)'\r'); buffer.write((byte)'\n'); return buffer; }); } protected Mono<DataBuffer> generateNewLine(DataBufferFactory bufferFactory) { return Mono.fromCallable(() -> { DataBuffer buffer = bufferFactory.allocateBuffer(2); buffer.write((byte)'\r'); buffer.write((byte)'\n'); return buffer; }); } protected Mono<DataBuffer> generateLastLine(byte[] boundary, DataBufferFactory bufferFactory) { return Mono.fromCallable(() -> { DataBuffer buffer = bufferFactory.allocateBuffer(boundary.length + 6); buffer.write((byte)'-'); buffer.write((byte)'-'); buffer.write(boundary); buffer.write((byte)'-'); buffer.write((byte)'-'); buffer.write((byte)'\r'); buffer.write((byte)'\n'); return buffer; }); } protected Mono<DataBuffer> generatePartHeaders(HttpHeaders headers, DataBufferFactory bufferFactory) { return Mono.fromCallable(() -> { DataBuffer buffer = bufferFactory.allocateBuffer(); for (Map.Entry<String, List<String>> entry : headers.entrySet()) { byte[] headerName = entry.getKey().getBytes(getCharset()); for (String headerValueString : entry.getValue()) { byte[] headerValue = headerValueString.getBytes(getCharset()); buffer.write(headerName); buffer.write((byte)':'); buffer.write((byte)' '); buffer.write(headerValue); buffer.write((byte)'\r'); buffer.write((byte)'\n'); } } buffer.write((byte)'\r'); buffer.write((byte)'\n'); return buffer; }); } }