001/* 002 * Copyright (c) 2010-2026 Mark Allen, Norbert Bartels. 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a copy 005 * of this software and associated documentation files (the "Software"), to deal 006 * in the Software without restriction, including without limitation the rights 007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 008 * copies of the Software, and to permit persons to whom the Software is 009 * furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 020 * THE SOFTWARE. 021 */ 022package com.restfb.request; 023 024import java.io.Closeable; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.net.http.HttpRequest.BodyPublisher; 029import java.nio.charset.StandardCharsets; 030import java.util.List; 031 032import com.restfb.BinaryAttachment; 033import com.restfb.util.StringUtils; 034 035/** 036 * Writes multipart/form-data requests to a temporary file and exposes them as a BodyPublisher. 037 */ 038public class MultipartFormBodyPublisher implements Closeable { 039 040 private static final byte[] MULTIPART_CARRIAGE_RETURN_AND_NEWLINE = "\r\n".getBytes(StandardCharsets.UTF_8); 041 private static final byte[] MULTIPART_TWO_HYPHENS = "--".getBytes(StandardCharsets.UTF_8); 042 043 private final TempFileBodyPublisher tempFileBodyPublisher; 044 private final OutputStream outputStream; 045 private final String boundary; 046 private final int bufferSize; 047 048 public MultipartFormBodyPublisher(String boundary, int bufferSize) throws IOException { 049 this.boundary = boundary; 050 this.bufferSize = bufferSize; 051 tempFileBodyPublisher = new TempFileBodyPublisher(); 052 outputStream = tempFileBodyPublisher.outputStream(); 053 } 054 055 public void addAttachments(List<BinaryAttachment> attachments) throws IOException { 056 for (BinaryAttachment attachment : attachments) { 057 addAttachment(attachment); 058 } 059 } 060 061 private void addAttachment(BinaryAttachment attachment) throws IOException { 062 StringBuilder headers = new StringBuilder(); 063 headers.append(new String(MULTIPART_TWO_HYPHENS, StandardCharsets.UTF_8)).append(boundary) 064 .append(new String(MULTIPART_CARRIAGE_RETURN_AND_NEWLINE, StandardCharsets.UTF_8)) 065 .append("Content-Disposition: form-data; name=\"").append(attachment.getFormFieldName()).append("\"; filename=\"") 066 .append(attachment.getFilename()).append("\"") 067 .append(new String(MULTIPART_CARRIAGE_RETURN_AND_NEWLINE, StandardCharsets.UTF_8)).append("Content-Type: ") 068 .append(attachment.getContentType()) 069 .append(new String(MULTIPART_CARRIAGE_RETURN_AND_NEWLINE, StandardCharsets.UTF_8)) 070 .append(new String(MULTIPART_CARRIAGE_RETURN_AND_NEWLINE, StandardCharsets.UTF_8)); 071 072 outputStream.write(headers.toString().getBytes(StringUtils.ENCODING_CHARSET)); 073 try (InputStream data = attachment.getData()) { 074 write(data, outputStream); 075 } 076 outputStream.write((new String(MULTIPART_CARRIAGE_RETURN_AND_NEWLINE, StandardCharsets.UTF_8) 077 + new String(MULTIPART_TWO_HYPHENS, StandardCharsets.UTF_8) + boundary 078 + new String(MULTIPART_TWO_HYPHENS, StandardCharsets.UTF_8) 079 + new String(MULTIPART_CARRIAGE_RETURN_AND_NEWLINE, StandardCharsets.UTF_8)) 080 .getBytes(StringUtils.ENCODING_CHARSET)); 081 } 082 083 private void write(InputStream source, OutputStream destination) throws IOException { 084 byte[] buffer = new byte[bufferSize]; 085 int read; 086 while ((read = source.read(buffer)) > 0) { 087 destination.write(buffer, 0, read); 088 } 089 } 090 091 public BodyPublisher build() throws IOException { 092 outputStream.flush(); 093 outputStream.close(); 094 return tempFileBodyPublisher.build(); 095 } 096 097 @Override 098 public void close() throws IOException { 099 tempFileBodyPublisher.close(); 100 } 101}