web-dev-qa-db-fra.com

Comment forcer URIBuilder.path (...) à encoder des paramètres comme "% AD"? Cette méthode n'encode pas toujours les paramètres avec un pourcentage, correctement

Comment forcer URIBuilder.path(...) à encoder des paramètres comme "%AD"?

Les méthodes path, replacePath et segment de URIBuilder ne codent pas toujours correctement les paramètres avec pourcentage.

Lorsqu'un paramètre contient le caractère "%" suivi de deux caractères qui forment ensemble un caractère codé URL, le "%" n'est pas codé comme "% 25".

Par exemple

URI uri = UriBuilder.fromUri("https://dummy.com").queryParam("param", "%AD");
String test = uri.build().toString();

"test" est " https://dummy.com?param=%AD "
Mais cela devrait être " https://dummy.com?param=%25AD " (avec le caractère "%" encodé en "% 25")

La méthode UriBuilderImpl.queryParam(...) se comporte comme ceci lorsque les deux caractères suivant le "%" sont hexadécimaux. C'est-à-dire que la méthode "com.Sun.jersey.api.uri.UriComponent.isHexCharacter (char)" renvoie true pour les caractères suivant le "%".

Je pense que le comportement d'UriBuilderImpl est correct car je suppose qu'il essaie de ne pas encoder les paramètres qui sont déjà encodés. Mais dans mon scénario, je n'essaierai jamais de créer des URL avec des paramètres déjà encodés.

Que dois-je faire?

Mon application Web utilise Jersey et dans de nombreux endroits, je crée des URI à l'aide de la classe UriBuilder ou j'appelle la méthode getBaseUriBuilder des objets UriInfo.

Je peux remplacer "%" par "% 25", chaque fois que j'appelle les méthodes queryParam, replaceQueryParam ou segment. Mais je recherche une solution moins lourde.

Comment puis-je faire en sorte que Jersey retourne ma propre implémentation d'UriBuilder?

J'ai pensé à créer une classe qui étend UriBuilderImpl qui écrase ces méthodes et qui effectue ce remplacement avant d'appeler super.queryParam(...) ou autre chose.

Existe-t-il un moyen de faire en sorte que Jersey retourne mon propre UriBuilder au lieu d'UriBuilderImpl, lors de l'appel de UriBuilder.fromURL(...), UriInfo.getBaseUriBuilder (...), etc.?

En regardant la méthode RuntimeDelegate, j'ai pensé étendre RuntimeDelegateImpl. Mon implémentation remplacerait la méthode createUriBuilder(...), qui retournerait la mienne UriBuilder, au lieu de UriBuilderImpl. Ensuite, j'ajouterais le fichier META-INF/services/javax.ws.rs.ext.RuntimeDelegate Et dedans, le nom complet de la classe de mon RuntimeDelegateImpl.

Le problème est que le jersey-bundle.jar contient déjà un META-INF/services/javax.ws.rs.ext.RuntimeDelegate Qui pointe vers com.Sun.jersey.server.impl.provider.RuntimeDelegateImpl, Donc le conteneur charge ce fichier au lieu de mon javax.ws.rs.ext.RuntimeDelegate. Par conséquent, il ne charge pas mon implémentation RuntimeDelegate.

Est-il possible de fournir ma propre implémentation de RuntimeDelegate?

Dois-je adopter une approche différente?

18
Montecarlo

UriBuilder

Ceci est possible avec l'aide de riComponent de Jersey ou RLEncoder directement de Java:

UriBuilder.fromUri("https://dummy.com")
        .queryParam("param",
                UriComponent.encode("%AD",
                    UriComponent.Type.QUERY_PARAM_SPACE_ENCODED))
        .build();

Ce qui se traduit par:

https://dummy.com/?param=%25AD

Ou:

UriBuilder.fromUri("https://dummy.com")
        .queryParam("param", URLEncoder.encode("%AD", "UTF-8"))
        .build()

Aura pour résultat:

https://dummy.com/?param=%25AD

Pour des exemples plus complexes (c'est-à-dire l'encodage JSON dans les paramètres de requête), cette approche est également possible. Supposons que vous ayez un JSON comme {"Entity":{"foo":"foo","bar":"bar"}}. Lorsqu'il est codé à l'aide de UriComponent, le résultat du paramètre de requête devrait ressembler à:

https://dummy.com/?param=%7B%22Entity%22:%7B%22foo%22:%22foo%22,%22bar%22:%22bar%22%7D%7D

De tels fichiers JSON pourraient même être injectés via @QueryParam dans le champ de ressource/paramètre de méthode (voir JSON dans les paramètres de requête ou comment injecter personnalisé Java via les annotations de paramètres JAX-RS ).


Quelle version de Jersey utilisez-vous? Dans les balises, vous mentionnez Jersey 2, mais dans la section RuntimeDelegate, vous utilisez des éléments Jersey 1.

36
Michal Gajdos

Vérifiez si les exemples suivants vous aident. Le fil lié ci-dessous a une discussion approfondie sur les fonctions disponibles et leurs différentes sorties.

Le suivant:

  1. UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").build("%20");
  2. UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").buildFromEncoded("%20");
  3. UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).build("%20");
  4. UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).buildFromEncoded("%20");

Sortira:

  1. http://localhost:8080?name=%2520
  2. http://localhost:8080?name=%20
  3. http://localhost:8080?name=%2520
  4. http://localhost:8080?name=%20

via http://comments.gmane.org/gmane.comp.Java.jsr311.user/71

En outre, basé sur la documentation de Class UriBuilder , l'exemple suivant montre comment obtenir ce que vous recherchez.

Les modèles d'URI sont autorisés dans la plupart des composants d'un URI mais leur valeur est limitée à un composant particulier. Par exemple.

UriBuilder.fromPath("{arg1}").build("foo#bar");

entraînerait le codage du '#' de telle sorte que l'URI résultant est "foo% 23bar". Pour créer un URI "foo # bar", utilisez

UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")

au lieu. Les noms et délimiteurs de modèle d'URI ne sont jamais codés, mais leurs valeurs sont codées lors de la création d'un URI. Les expressions régulières des paramètres de modèle sont ignorées lors de la création d'un URI, c'est-à-dire qu'aucune validation n'est effectuée.

6
JSuar

Il est possible d'écraser le comportement par défaut dans le maillot manuellement au démarrage, par ex. avec une aide statique qui appelle RuntimeDelegate.setInstance(yourRuntimeDelegateImpl).

Donc, si vous voulez avoir un UriBuilder qui encode les pourcentages même s'ils semblent faire partie d'une séquence déjà encodée, cela ressemblerait à:

[...]
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.RuntimeDelegate;

import com.Sun.jersey.api.uri.UriBuilderImpl;
import com.Sun.ws.rs.ext.RuntimeDelegateImpl;
// or for jersey2:
// import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
// import org.glassfish.jersey.internal.RuntimeDelegateImpl;

public class SomeBaseClass {

    [...]

    // this is the lengthier custom implementation of UriBuilder
    // replace this with your own according to your needs
    public static class AlwaysPercentEncodingUriBuilder extends UriBuilderImpl {

        @Override
        public UriBuilder queryParam(String name, Object... values) {
            Object[] encValues = new Object[values.length];
            for (int i=0; i<values.length; i++) {
                String value = values[i].toString(); // TODO: better null check here, like in base class
                encValues[i] = percentEncode(value);
            }
            return super.queryParam(name, encValues);
        }

        private String percentEncode(String value) {
            StringBuilder sb = null;
            for (int i=0;  i < value.length(); i++) {
                char c = value.charAt(i);
                // if this condition is is true, the base class will not encode the percent
                if (c == '%' 
                    && i + 2 < value.length()
                    && isHexCharacter(value.charAt(i + 1)) 
                    && isHexCharacter(value.charAt(i + 2))) {
                    if (sb == null) {
                        sb = new StringBuilder(value.substring(0, i));
                    }
                    sb.append("%25");
                } else {
                    if (sb != null) sb.append(c);
                }
            }
            return (sb != null) ? sb.toString() : value;
        }

        // in jersey2 one can call public UriComponent.isHexCharacter
        // but in jersey1 we need to provide this on our own
        private static boolean isHexCharacter(char c) {
            return ('0' <= c && c <= '9')
                || ('A' <=c && c <= 'F')
                || ('a' <=c && c <= 'f');
        }
    }

    // here starts the code to hook up the implementation
    public static class AlwaysPercentEncodingRuntimeDelegateImpl extends RuntimeDelegateImpl {
        @Override
        public UriBuilder createUriBuilder() {
            return new AlwaysPercentEncodingUriBuilder();
        }
    }

    static {
        RuntimeDelegate myDelegate = new AlwaysPercentEncodingRuntimeDelegateImpl();
        RuntimeDelegate.setInstance(myDelegate);
    }

}

Mise en garde: Bien sûr, de cette façon, il n'est pas très configurable, et si vous le faites dans un code de bibliothèque qui pourrait être réutilisé par d'autres, cela pourrait provoquer une certaine irritation.

Par exemple, j'ai eu le même problème que l'OP lors de l'écriture d'un client de repos dans un plugin Confluence, et je me suis retrouvé avec la solution "encoder manuellement chaque paramètre" à la place, car les plugins sont chargés via OSGi et ne sont donc tout simplement pas en mesure de toucher le RuntimeDelegateImpl (obtention de Java.lang.ClassNotFoundException: com.Sun.ws.rs.ext.RuntimeDelegateImpl lors de l'exécution à la place).

(Et pour mémoire, en jersey2, cela semble très similaire; en particulier, le code pour accrocher le RuntimeDelegateImpl personnalisé est le même.)