web-dev-qa-db-fra.com

Comment décoder correctement les paramètres unicode transmis à un servlet

Supposons que j'ai:

<a href="http://www.yahoo.com/" target="_yahoo" 
    title="Yahoo!&#8482;" onclick="return gateway(this);">Yahoo!</a>
<script type="text/javascript">
function gateway(lnk) {
    window.open(SERVLET +
        '?external_link=' + encodeURIComponent(lnk.href) +
        '&external_target=' + encodeURIComponent(lnk.target) +
        '&external_title=' + encodeURIComponent(lnk.title));
    return false;
}
</script>

J'ai confirmé que external_title est codé en tant que Yahoo!%E2%84%A2 et transmis à SERVLET. Si dans SERVLET je fais:

Writer writer = response.getWriter();
writer.write(request.getParameter("external_title"));

Je reçois Yahoo! ™ dans le navigateur. Si je commute manuellement le codage de caractères du navigateur sur UTF-8, il devient Yahoo!TM (c'est ce que je veux).

J'ai donc pensé que l'encodage que j'envoyais au navigateur était incorrect (c'était Content-type: text/html; charset=ISO-8859-1). J'ai changé SERVLET en:

response.setContentType("text/html; charset=utf-8");
Writer writer = response.getWriter();
writer.write(request.getParameter("external_title"));

Le codage des caractères du navigateur est maintenant UTF-8, mais il génère Yahoo! • et je ne peux pas obtenir du navigateur le rendu du caractère correct.

Ma question est la suivante: existe-t-il une combinaison de Content-type et/ou new String(request.getParameter("external_title").getBytes(), "UTF-8"); et/ou de quelque chose d'autre qui entraînera Yahoo!TM figurant dans la sortie SERVLET?

35
Grant Wagner

Vous êtes presque là. EncodeURIComponent code correctement en UTF-8, ce que vous devriez toujours utiliser dans une URL aujourd'hui.

Le problème est que la chaîne de requête soumise est mutilée en chemin dans votre script côté serveur, car getParameter () utilise ISO-8559-1 au lieu de UTF-8. Cela provient d'Antical Times avant que le Web ait opté pour UTF-8 pour URI/IRI, mais il est plutôt pathétique que la spécification Servlet n'ait pas été mise à jour pour correspondre à la réalité, ou au moins pour fournir une option fiable et prise en charge.

(Il y a request.setCharacterEncoding dans Servlet 2.3, mais cela n'affecte pas l'analyse syntaxique des chaînes de requête et, si un seul paramètre a déjà été lu, éventuellement par un autre élément du cadre, cela ne fonctionnera pas du tout.)

Vous devez donc utiliser des méthodes spécifiques à chaque conteneur pour obtenir le bon UTF-8, impliquant souvent des éléments dans server.xml. Cela craint totalement pour la distribution d'applications Web qui devraient fonctionner n'importe où. Pour Tomcat, voir http://wiki.Apache.org/Tomcat/FAQ/CharacterEncoding et aussi Quelle est la différence entre "URIEncoding" de Tomcat, filtre de codage et request.setCharacterEncoding .

44
bobince

J'ai eu le même problème et l'ai résolu en décodant Request.getQueryString() en utilisant URLDecoder (), et après avoir extrait mes paramètres.

String[] Parameters = URLDecoder.decode(Request.getQueryString(), 'UTF-8')
                       .splitat('&');
18
Modi

Il y a moyen de le faire en Java (pas de bidouillage avec server.xml)

Ne fonctionnent pas :

protected static final String CHARSET_FOR_URL_ENCODING = "UTF-8";

String uname = request.getParameter("name");
System.out.println(uname);
// ÏηγÏÏÏÏη
uname = request.getQueryString();
System.out.println(uname);
// name=%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7
uname = URLDecoder.decode(request.getParameter("name"),
        CHARSET_FOR_URL_ENCODING);
System.out.println(uname);
// ÏηγÏÏÏÏη // !!!!!!!!!!!!!!!!!!!!!!!!!!!
uname = URLDecoder.decode(
        "name=%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7",
        CHARSET_FOR_URL_ENCODING);
System.out.println("query string decoded : " + uname);
// query string decoded : name=τηγρτσςη
uname = URLDecoder.decode(new String(request.getParameter("name")
        .getBytes()), CHARSET_FOR_URL_ENCODING);
System.out.println(uname);
// ÏηγÏÏÏÏη // !!!!!!!!!!!!!!!!!!!!!!!!!!!

Travaux :

final String name = URLDecoder
        .decode(new String(request.getParameter("name").getBytes(
                "iso-8859-1")), CHARSET_FOR_URL_ENCODING);
System.out.println(name);
// τηγρτσςη

Fonctionné mais cassera si l'encodage par défaut! = Utf-8 - essayez ceci à la place (omettez l'appel à decode () ce n'est pas nécessaire):

final String name = new String(request.getParameter("name").getBytes("iso-8859-1"),
        CHARSET_FOR_URL_ENCODING);

Comme je l'ai dit ci-dessus, si le server.xml est modifié comme dans:

<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1"
                     redirectPort="8443"  URIEncoding="UTF-8"/> 

(remarquez le URIEncoding="UTF-8") le code ci-dessus va casser (car le getBytes("iso-8859-1") devrait se lire getBytes("UTF-8")). Donc, pour une solution à l'épreuve des balles, vous devez obtenir la valeur de l'attribut URIEncoding. Malheureusement, cela semble être spécifique à un conteneur, voire pire, à la version du conteneur. Pour Tomcat 7, vous avez besoin de quelque chose comme:

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.Apache.catalina.Server;
import org.Apache.catalina.Service;
import org.Apache.catalina.connector.Connector;

public class Controller extends HttpServlet {

    // ...
    static String CHARSET_FOR_URI_ENCODING; // the `URIEncoding` attribute
    static {
        MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(
            0);
        ObjectName name = null;
        try {
            name = new ObjectName("Catalina", "type", "Server");
        } catch (MalformedObjectNameException e1) {
            e1.printStackTrace();
        }
        Server server = null;
        try {
            server = (Server) mBeanServer.getAttribute(name, "managedResource");
        } catch (AttributeNotFoundException | InstanceNotFoundException
                | MBeanException | ReflectionException e) {
            e.printStackTrace();
        }
        Service[] services = server.findServices();
        for (Service service : services) {
            for (Connector connector : service.findConnectors()) {
                System.out.println(connector);
                String uriEncoding = connector.getURIEncoding();
                System.out.println("URIEncoding : " + uriEncoding);
                boolean use = connector.getUseBodyEncodingForURI();
                // TODO : if(use && connector.get uri enc...)
                CHARSET_FOR_URI_ENCODING = uriEncoding;
                // ProtocolHandler protocolHandler = connector
                // .getProtocolHandler();
                // if (protocolHandler instanceof Http11Protocol
                // || protocolHandler instanceof Http11AprProtocol
                // || protocolHandler instanceof Http11NioProtocol) {
                // int serverPort = connector.getPort();
                // System.out.println("HTTP Port: " + connector.getPort());
                // }
            }
        }
    }
}

Et encore vous devez Tweak ceci pour plusieurs connecteurs (vérifiez les parties commentées). Ensuite, vous utiliseriez quelque chose comme:

new String(parameter.getBytes(CHARSET_FOR_URI_ENCODING), CHARSET_FOR_URL_ENCODING);

Néanmoins, cela peut échouer ( IIUC ) si parameter = request.getParameter("name"); décodé avec CHARSET_FOR_URI_ENCODING était corrompu et que les octets obtenus avec getBytes () n'étaient pas les originaux (c'est pourquoi "iso-8859-1" est utilisé par défaut - il préservera les octets ). Vous pouvez vous en débarrasser en analysant manuellement la chaîne de requête dans les lignes suivantes:

URLDecoder.decode(request.getQueryString().split("=")[1],
        CHARSET_FOR_URL_ENCODING);

Je suis toujours à la recherche de l'emplacement dans la documentation où il est mentionné que request.getParameter("name") appelle URLDecoder.decode() au lieu de renvoyer la chaîne %CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7? Un lien dans la source serait très apprécié.
Aussi, comment puis-je passer comme valeur du paramètre la chaîne, par exemple, %CE? => voir le commentaire: parameter=%25CE

15
Mr_and_Mrs_D

Je suspecte que la mutilation de données se produise dans la demande, c’est-à-dire que le codage déclaré de la demande ne correspond pas à celui qui est réellement utilisé pour les données.

Que retourne request.getCharacterEncoding()?

Je ne sais pas vraiment comment JavaScript gère les encodages ni comment le faire en utiliser un spécifique.

Vous devez vous assurer que les codages sont utilisés correctement à toutes les étapes - n'essayez PAS de "réparer" les données en utilisant new String() et getBytes() à un endroit où elles ont déjà été codées de manière incorrecte.

Edit: Il peut être utile d’avoir aussi la page Origin (celle avec le Javascript) encodée en UTF-8 et déclarée comme telle dans son Content-Type. Ensuite, je pense que Javascript peut utiliser par défaut UTF-8 pour sa requête, mais ce n’est pas une connaissance précise, mais des suppositions.

2
Michael Borgwardt

Je pense que je peux obtenir les éléments suivants au travail:

encodeURIComponent(escape(lnk.title))

Cela me donne %25u2122 (pour & # 8482) ou %25AE (pour & # 174), qui décodera en %u2122 et %AE respectivement dans le servlet.

Je devrais alors pouvoir transformer% u2122 en '\u2122' et% AE en '\u00AE' relativement facilement en utilisant (char) (base-10 integer value of %uXXXX or %XX) dans une correspondance et en remplaçant la boucle à l'aide d'expressions régulières.

c.-à-d. match /%u([0-9a-f]{4})/i, extrayez la sous-expression correspondante, convertissez-la en base 10, transformez-la en caractère et ajoutez-la à la sortie, puis procédez de même avec /%([0-9a-f]{2})/i

0
Grant Wagner

Il existe un bogue dans certaines versions de Jetty qui lui permet d’analyser de façon incorrecte les caractères UTF-8 de nombres plus élevés. Si votre serveur accepte correctement les lettres arabes mais pas emoji, cela signifie que vous avez une version présentant ce problème, car l'arabe n'est pas dans ISO-8859-1, mais se situe dans la plage inférieure des caractères UTF-8 ("lower" signifiant Java le représentera en un seul caractère).

J'ai mis à jour la version 7.2.0.v20101020 vers la version 7.5.4.v20111024 et le problème a été résolu. Je peux maintenant utiliser la méthode getParameter (String) au lieu d'avoir à l'analyser moi-même.

Si vous êtes vraiment curieux, vous pouvez creuser dans votre version de org.Eclipse.jetty.util.Utf8StringBuilder.append (octet) et voir s’il ajoute correctement plusieurs caractères à la chaîne lorsque le code utf-8 est suffisamment élevé , comme dans 7.2.0, il jette simplement un int sur un caractère et l'ajoute.

0
Ben B

Vous pouvez toujours utiliser javascript pour manipuler davantage le texte. 

<div id="test">a</div>
<script>
var a = document.getElementById('test');
alert(a.innerHTML);
a.innerHTML = decodeURI("Yahoo!%E2%84%A2");
alert(a.innerHTML);
</script>
0
jacobangel