package com.vtence.molecule.testing.http;
import com.vtence.molecule.helpers.Streams;
import com.vtence.molecule.http.ContentType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class MultipartForm extends Form {
public static String CRLF = "\r\n";
private final String boundary = Long.toHexString(System.currentTimeMillis());
private Charset charset = StandardCharsets.UTF_8;
@Override
public long contentLength() throws IOException {
ByteCountingOutputStream out = new ByteCountingOutputStream();
writeTo(out);
return out.byteCount();
}
public String contentType() {
return "multipart/form-data" + "; boundary=" + boundary;
}
public MultipartForm charset(String charsetName) {
return charset(Charset.forName(charsetName));
}
public MultipartForm charset(Charset charset) {
this.charset = charset;
return this;
}
public MultipartForm addField(Field field) {
super.addField(field);
return this;
}
public MultipartForm addField(String name, String value) {
return addField(new TextField(name, value));
}
public MultipartForm addTextFile(String name, File file) {
return addTextFile(name, file, guessMimeType(file));
}
public MultipartForm addTextFile(String name, File file, String contentType) {
return addField(new FileUpload(name, file, contentType, false));
}
public MultipartForm addBinaryFile(String name, File toUpload) {
return addBinaryFile(name, toUpload, guessMimeType(toUpload));
}
public MultipartForm addBinaryFile(String name, File toUpload, String contentType) {
return addField(new FileUpload(name, toUpload, contentType, true));
}
public void writeTo(OutputStream out) throws IOException {
Writer writer = new OutputStreamWriter(out, charset);
for (Field field : fields) {
writer.append("--").append(boundary).append(CRLF);
writer.flush();
field.encode(out, charset);
}
writer.append("--").append(boundary).append("--").append(CRLF);
writer.flush();
}
private String guessMimeType(File file) {
return URLConnection.guessContentTypeFromName(file.getName());
}
static class TextField implements Field {
public final String name;
public final String value;
public TextField(String name, String value) {
this.name = name;
this.value = value;
}
@Override
public void encode(OutputStream out, Charset charset) throws IOException {
Writer writer = new OutputStreamWriter(out, charset);
URLEscaper escaper = URLEscaper.to(charset);
writer.append("Content-Disposition: form-data; name=\"").append(escaper.escape(name)).append("\"").append(CRLF);
writer.append(CRLF);
writer.append(value).append(CRLF);
writer.flush();
}
}
static class FileUpload implements Field {
private final String name;
private final File file;
private final String contentType;
private final boolean binary;
public FileUpload(String name, File file, String contentType, boolean binary) {
this.name = name;
this.file = file;
this.contentType = contentType;
this.binary = binary;
}
public void encode(OutputStream out, Charset charset) throws IOException {
Writer writer = new OutputStreamWriter(out, charset);
URLEscaper escaper = URLEscaper.to(charset);
writer.append("Content-Disposition: form-data");
if (name != null) {
writer.append("; name=\"").append(escaper.escape(name)).append("\"");
}
writer.append("; filename=\"").append(escaper.escape(file.getName())).append("\"").append(CRLF);
writer.append("Content-Type: ").append(contentType);
if (!binary) {
writer.append("; charset=").append(fileCharset().name().toLowerCase()).append(CRLF);
} else {
writer.append(CRLF);
writer.append("Content-Transfer-Encoding: binary").append(CRLF);
}
writer.append(CRLF);
writer.flush();
writeFileContent(out);
writer.append(CRLF);
writer.flush();
}
private void writeFileContent(OutputStream output) throws IOException {
try (FileInputStream input = new FileInputStream(file)) {
Streams.copy(input, output);
}
}
private Charset fileCharset() {
ContentType contentType = ContentType.parse(this.contentType);
if (contentType == null || contentType.charset() == null) return StandardCharsets.UTF_8;
return contentType.charset();
}
}
}