Fix for Jersey’s russian files names bug

In our project we use Sun/Oracle implementation of JAX-RS — Jersey ’cause it’s greate framework for build REST-services. But there are some troubles with uploading files feature. When you uploading file with latin chars in file name everything is fine, you read this file name on server and it’s exactly the same as on client computer. But when you trying to upload file with russian chars in file name you get “abracadabra” instead of them. For example:

Original file name Received file name
Test123.txt Test123.txt
Тест123.txt Тест123.txt


I found that is not exactly Jersey problem but MIME pull which Jersey using to parse multi part requests. The problem is that the MIME pull reads headers in non UTF8 encoding. And the solution is to encode header’s data in UTF8.

I wrote Jersey’s custom multi part reader provider using built in multi part reader sources (MultiPartReader class) as base. The only thing I had to change — add a function getFixedHeaderValue which fixes headers’ encoding and used it in read headers loop:

for (Header h : mp.getAllHeaders()) {
    bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h));
}

Now everything works perfectly)

P.S.: This solution works only when you using Jersey’s multipart module

Usage example:

@POST
@Path("/upload-file/")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadFile(@FormDataParam("Filedata") FormDataBodyPart newFileBodyPart) {
    ...
    String fileName = newFileBodyPart.getContentDisposition().getFileName();
    ...
}

Listing of my custom multi part reader provider:

/**
 * Replaces build-in Jersey multipart reader to fix russian files names problem.
 *
 * @see com.sun.jersey.multipart.impl.MultiPartReader
 */
@Provider
@Consumes("multipart/*")
public class UTF8MultiPartReader implements MessageBodyReader<MultiPart> {
 
    private final Providers providers;
 
    private final MultiPartConfig config;
 
    private final CloseableService closeableService;
 
    private final MIMEConfig mimeConfig;
 
    // -------------------------------------------------------
    // -                        LOGIC                        -
    // -------------------------------------------------------
 
    private String getFixedHeaderValue(Header h) {
        String result = h.getValue();
 
        if (h.getName().equals("Content-Disposition") && (result.indexOf("filename=") != -1)) {
            try {
                result = new String(result.getBytes(), "utf8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("Can't convert header "Content-Disposition" to UTF8 format.");
            }
        }
 
        return result;
    }
 
    public UTF8MultiPartReader(@Context Providers providers, @Context MultiPartConfig config,
        @Context CloseableService closeableService) {
        this.providers = providers;
 
        if (config == null) {
            throw new IllegalArgumentException("The MultiPartConfig instance we expected is not present. "
                + "Have you registered the MultiPartConfigProvider class?");
        }
 
        this.config = config;
        this.closeableService = closeableService;
 
        mimeConfig = new MIMEConfig();
        mimeConfig.setMemoryThreshold(config.getBufferThreshold());
    }
 
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return MultiPart.class.isAssignableFrom(type);
    }
 
    public MultiPart readFrom(Class<MultiPart> type, Type genericType, Annotation[] annotations, MediaType mediaType,
        MultivaluedMap<String, String> headers, InputStream stream) throws IOException, WebApplicationException {
        try {
            MIMEMessage mm = new MIMEMessage(stream, mediaType.getParameters().get("boundary"), mimeConfig);
 
            boolean formData = false;
            MultiPart multiPart = null;
 
            if (MediaTypes.typeEquals(mediaType, MediaType.MULTIPART_FORM_DATA_TYPE)) {
                multiPart = new FormDataMultiPart();
                formData = true;
            } else {
                multiPart = new MultiPart();
            }
 
            multiPart.setProviders(providers);
 
            if (!formData) {
                multiPart.setMediaType(mediaType);
            }
 
            for (MIMEPart mp : mm.getAttachments()) {
                BodyPart bodyPart = null;
 
                if (formData) {
                    bodyPart = new FormDataBodyPart();
                } else {
                    bodyPart = new BodyPart();
                }
 
                bodyPart.setProviders(providers);
 
                for (Header h : mp.getAllHeaders()) {
                    bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h));
                }
 
                try {
                    String contentType = bodyPart.getHeaders().getFirst("Content-Type");
 
                    if (contentType != null) {
                        bodyPart.setMediaType(MediaType.valueOf(contentType));
                    }
 
                    bodyPart.getContentDisposition();
                } catch (IllegalArgumentException ex) {
                    throw new WebApplicationException(ex, 400);
                }
 
                bodyPart.setEntity(new BodyPartEntity(mp));
                multiPart.getBodyParts().add(bodyPart);
            }
 
            if (closeableService != null) {
                closeableService.add(multiPart);
            }
 
            return multiPart;
        } catch (MIMEParsingException ex) {
            throw new WebApplicationException(ex, 400);
        }
    }
 
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.