eclipse-ee4j / jersey

Eclipse Jersey Project - Read our Wiki:
https://github.com/eclipse-ee4j/jersey/wiki
Other
691 stars 353 forks source link

Preserve whitespace in Content-Type header #5641

Closed jluehe closed 4 months ago

jluehe commented 5 months ago

Has there been any consideration given to restore/preserve the whitespace character separating the charset= from the semicolon (trailing the MIME type) in the Content-Type header? Or could this be made configurable at a minimum?

See https://download.oracle.com/javaee-archive/jersey.java.net/users/2013/04/17157.html where this was brought up originally.

Apologies if this was already discussed.

Thank you!

jansupol commented 5 months ago

I believe this is one of the tasks that should have been solvable by HeaderDelegate and associated HeaderDelegateProvider SPI, so that each customer can specify whatever he desires. The SPI has been a bit overlooked in the past, but that's the idea. Would it help in your case?

jluehe commented 5 months ago

Thank you, @jansupol! That sounds like an interesting idea! Let me play with it and see if I can get it to work!

jluehe commented 4 months ago

@jansupol, I was able to pinpoint where the whitespace gets stripped in Jersey 2:

package com.sun.jersey.core.impl.provider.header;

public class MediaTypeProvider implements HeaderDelegateProvider<MediaType> {

    @Override
    public String toString(MediaType header) {
        StringBuilder b = new StringBuilder();
        b.append(header.getType()).append('/').append(header.getSubtype());
        for (Map.Entry<String, String> e : header.getParameters().entrySet()) {
            b.append("; ").append(e.getKey()).append('=');    <<<<== whitespace added after semicolon
            WriterUtil.appendQuotedIfNonToken(b, e.getValue());
        }
        return b.toString();
    }

    ...
}

https://raw.githubusercontent.com/eclipse-ee4j/jersey/2.x/core-common/src/main/java/org/glassfish/jersey/message/internal/MediaTypeProvider.java:

package org.glassfish.jersey.message.internal;

public class MediaTypeProvider implements HeaderDelegateProvider<MediaType> {

    @Override
    public String toString(MediaType header) {

        throwIllegalArgumentExceptionIfNull(header, MEDIA_TYPE_IS_NULL);

        StringBuilder b = new StringBuilder();
        b.append(header.getType()).append('/').append(header.getSubtype());
        for (Map.Entry<String, String> e : header.getParameters().entrySet()) {
            b.append(";").append(e.getKey()).append('=');    <<<<== no more whitespace added after semicolon
            StringBuilderUtils.appendQuotedIfNonToken(b, e.getValue());
        }
        return b.toString();
    }

    ...
}
jluehe commented 4 months ago

@jansupol, can you tell me how I can replace/override Jersey's MediaTypeProvider with my own?

I see Jersey's RuntimeDelegateImpl getting initialized as follows:

package org.glassfish.jersey.server.internal;

public RuntimeDelegateImpl() {
    super(new MessagingBinders.HeaderDelegateProviders().getHeaderDelegateProviders());
}

where the MessagingBinders.HeaderDelegateProviders constructor uses Jersey's built-in HeaderDelegateProviders:

        public HeaderDelegateProviders() {
            Set<HeaderDelegateProvider> providers = new HashSet<>();
            providers.add(new CacheControlProvider());
            providers.add(new CookieProvider());
            providers.add(new DateProvider());
            providers.add(new EntityTagProvider());
            providers.add(new LinkProvider());
            providers.add(new LocaleProvider());
            providers.add(new MediaTypeProvider());
            providers.add(new NewCookieProvider());
            providers.add(new StringHeaderProvider());
            providers.add(new UriProvider());
            this.providers = providers;
        }

I had hoped it would also consider any custom HeaderDelegateProviders declared in META-INF/services as described here: https://eclipse-ee4j.github.io/jersey.github.io/apidocs/2.34/jersey/org/glassfish/jersey/spi/HeaderDelegateProvider.html

Do you have any recommendation for how to make this work? I noticed the same question was asked here: https://stackoverflow.com/questions/62297701/how-to-inject-custom-implementation-of-org-glassfish-jersey-spi-headerdelegatepr, but it was never answered.

Thank you!

jluehe commented 4 months ago

@jansupol, what I ended up doing is extend org.glassfish.jersey.server.internal.RuntimeDelegateImpl and have my subclass (declared in META-INF/services/javax.ws.rs.ext.RuntimeDelegate) override createHeaderDelegate ...