package org.jboss.resteasy.plugins.providers.multipart;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.MimeIOException;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.field.DefaultFieldParser;
import org.apache.james.mime4j.field.LenientFieldParser;
import org.apache.james.mime4j.message.BodyFactory;
import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder;
import org.apache.james.mime4j.message.MessageImpl;
import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.storage.AbstractStorageProvider;
import org.apache.james.mime4j.storage.DefaultStorageProvider;
import org.apache.james.mime4j.storage.Storage;
import org.apache.james.mime4j.storage.StorageBodyFactory;
import org.apache.james.mime4j.storage.StorageOutputStream;
import org.apache.james.mime4j.storage.StorageProvider;
import org.apache.james.mime4j.storage.ThresholdStorageProvider;
import org.apache.james.mime4j.stream.BodyDescriptorBuilder;
import org.apache.james.mime4j.stream.MimeConfig;
import org.jboss.resteasy.spi.config.ConfigurationFactory;

Copy code from org.apache.james.mime4j.message.DefaultMessageBuilder.parseMessage(). Alter said code to use Mime4JWorkaroundBinaryEntityBuilder instead of EntityBuilder.
/** * Copy code from org.apache.james.mime4j.message.DefaultMessageBuilder.parseMessage(). * Alter said code to use Mime4JWorkaroundBinaryEntityBuilder instead of EntityBuilder. */
public class Mime4JWorkaround {
This is a rough copy of DefaultMessageBuilder.parseMessage() modified to use a Mime4JWorkaround as the contentHandler instead of an EntityBuilder.

Params:
  • is –
Throws:
See Also:
Returns:
/** * This is a rough copy of DefaultMessageBuilder.parseMessage() modified to use a Mime4JWorkaround as the contentHandler instead * of an EntityBuilder. * <p> * * @param is * @return * @throws IOException * @throws MimeIOException * @see org.apache.james.mime4j.message.DefaultMessageBuilder#parseMessage(java.io.InputStream) */
public static Message parseMessage(InputStream is) throws IOException, MimeIOException { try { MessageImpl message = new MessageImpl(); MimeConfig cfg = MimeConfig.DEFAULT; boolean strict = cfg.isStrictParsing(); DecodeMonitor mon = strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT; BodyDescriptorBuilder bdb = new DefaultBodyDescriptorBuilder(null, strict ? DefaultFieldParser.getParser() : LenientFieldParser.getParser(), mon); StorageProvider storageProvider; if (ConfigurationFactory.getInstance().getConfiguration().getOptionalValue(DefaultStorageProvider.DEFAULT_STORAGE_PROVIDER_PROPERTY, String.class).orElse(null) != null) { storageProvider = DefaultStorageProvider.getInstance(); } else { StorageProvider backend = new CustomTempFileStorageProvider(); storageProvider = new ThresholdStorageProvider(backend, 1024); } BodyFactory bf = new StorageBodyFactory(storageProvider, mon); MimeStreamParser parser = new MimeStreamParser(cfg, mon, bdb); // EntityBuilder expect the parser will send ParserFields for the well known fields // It will throw exceptions, otherwise. parser.setContentHandler(new Mime4jWorkaroundBinaryEntityBuilder(message, bf)); parser.setContentDecoding(false); parser.setRecurse(); parser.parse(is); return message; } catch (MimeException e) { throw new MimeIOException(e); } }
A custom TempFileStorageProvider that do no set deleteOnExit on temp files, to avoid memory leaks (see https://issues.apache.org/jira/browse/MIME4J-251)
/** * A custom TempFileStorageProvider that do no set deleteOnExit on temp files, * to avoid memory leaks (see https://issues.apache.org/jira/browse/MIME4J-251) * */
private static class CustomTempFileStorageProvider extends AbstractStorageProvider { private static final String DEFAULT_PREFIX = "m4j"; private final String prefix; private final String suffix; private final File directory; CustomTempFileStorageProvider() { this(DEFAULT_PREFIX, null, null); } CustomTempFileStorageProvider(final String prefix, final String suffix, final File directory) { if (prefix == null || prefix.length() < 3) throw new IllegalArgumentException("invalid prefix"); if (directory != null && !directory.isDirectory() && !directory.mkdirs()) throw new IllegalArgumentException("invalid directory"); this.prefix = prefix; this.suffix = suffix; this.directory = directory; } public StorageOutputStream createStorageOutputStream() throws IOException { File file = File.createTempFile(prefix, suffix, directory); return new TempFileStorageOutputStream(file); } private static final class TempFileStorageOutputStream extends StorageOutputStream { private File file; private OutputStream out; TempFileStorageOutputStream(final File file) throws IOException { this.file = file; this.out = new FileOutputStream(file); } @Override public void close() throws IOException { super.close(); out.close(); } @Override protected void write0(byte[] buffer, int offset, int length) throws IOException { out.write(buffer, offset, length); } @Override protected Storage toStorage0() throws IOException { // out has already been closed because toStorage calls close return new TempFileStorage(file); } } private static final class TempFileStorage implements Storage { private File file; private static final Set<File> filesToDelete = new HashSet<File>(); TempFileStorage(final File file) { this.file = file; } public void delete() { // deleting a file might not immediately succeed if there are still // streams left open (especially under Windows). so we keep track of // the files that have to be deleted and try to delete all these // files each time this method gets invoked. // a better but more complicated solution would be to start a // separate thread that tries to delete the files periodically. synchronized (filesToDelete) { if (file != null) { filesToDelete.add(file); file = null; } for (Iterator<File> iterator = filesToDelete.iterator(); iterator.hasNext();) { File f = iterator.next(); if (f.delete()) { iterator.remove(); } } } } public InputStream getInputStream() throws IOException { if (file == null) throw new IllegalStateException("storage has been deleted"); return new BufferedInputStream(new FileInputStream(file)); } } } }