web-dev-qa-db-fra.com

Pourquoi est-ce que j'obtiens des caractères incorrects lors du décodage d'une chaîne Base64 en NVARCHAR dans SQL Server?

J'ai cherché à décoder Base64 avec SQL Server et après avoir recherché de nombreuses solutions en ligne (certaines d'ici) semblent être basées sur ce type de méthode.

SELECT CAST(CAST('Base64StringHere' as XML ).value('.','varbinary(max)') AS VARCHAR(250))

Quand j'ai du texte ASCII cela fonctionne parfaitement. Cependant, quand j'ai le texte français suivant, il est corrompu (probablement à cause des restrictions de VARCHAR).

Où est le café le plus proche?
T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=

Et fournit la sortie suivante.

Où est le café le plus proche?

Je pensais que la solution relativement simple serait de changer le CAST en NVARCHAR mais cela se traduit à nouveau par une corruption.

SELECT CAST(CAST('T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=' as XML ).value('.','varbinary(max)') AS NVARCHAR(250) )

썏₹獥⁴敬挠晡꧃氠⁥汰獵瀠潲档㽥

Mes compétences en moteur de recherche peuvent me manquer, mais je n'arrive pas à trouver quelqu'un d'autre qui a mon problème.

Des pensées?

4
Geesh_SO

Le problème est que vous avez encodé une chaîne encodée UTF-8 en Base64. Ainsi, le décodage de la Base64 vous rend la séquence d'octets UTF-8 d'origine. SQL Server utilise UTF-16 Little Endian uniquement pour les données NVARCHAR, et même pour XML. Par conséquent, ù est la version 8 bits de la séquence UTF-8 à deux octets pour ù (0xC3 et 0xB9).

Heureusement, il est possible de convertir une chaîne codée UTF-8 en UTF-16, ou même en une page de codes non Unicode (SI la page de codes prend en charge tous les caractères convertis). L'astuce consiste à convertir les octets décodés Base64, dans leur représentation textuelle (même avec des caractères incorrectement convertis) en XML. L'astuce pour cette astuce est que vous devez ajouter le <?xml ...> déclaration (généralement omise) et spécifiez l'encodage source:

DECLARE @Base64Value NVARCHAR(500) = N'T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=';

DECLARE @BinaryValue VARBINARY(500) =
    CONVERT(XML, @Base64Value).value('.','varbinary(max)');

DECLARE @IntermediaryValue VARCHAR(500) = CONVERT(VARCHAR(500), @BinaryValue);

SELECT @BinaryValue, @IntermediaryValue;
-- 0x4FC3B920657374206C6520636166C3A9206C6520706C75732070726F6368653F
-- Oֳ¹ est le cafֳ© le plus proche?


-- This is to NVARCHAR, which will always work:
SELECT CONVERT(NVARCHAR(500),
    CONVERT(XML, '<?xml version="1.0" encoding="UTF-8"?>' +  @IntermediaryValue)
              );
-- Où est le café le plus proche?


-- This is to VARCHAR, but "success" will depend on the Code Page
-- specified by the default Collation of the current Database:
SELECT CONVERT(VARCHAR(500),
    CONVERT(XML, '<?xml version="1.0" encoding="UTF-8"?>' +  @IntermediaryValue)
              );

 -- In a DB with a Latin1_General Collation it works:
 -- Où est le café le plus proche?


 -- In a DB with a Hebrew Collation, it gets the following error:
 /*
   Msg 6355, Level 16, State 1, Line XXXXX
   Conversion of one or more characters from XML to target collation impossible
 */

Veuillez noter que cette astuce ne fonctionne que pour la conversion à partir de divers encodages source en UTF-16 Little Endian (car c'est ainsi que le type de données XML dans SQL Server stocke les chaînes en interne). Cette méthode ne peut pas être utilisée pour convertir UTF-16 en UTF-8 ou un autre encodage non pris en charge par SQL Server.


Voici un Inline-TVF encapsulant les étapes ci-dessus:

GO
CREATE FUNCTION dbo.ConvertBase64EncodedUTF8ToUTF16LE
(
  @Base64EncodedUTF8String VARCHAR(8000)
)
RETURNS TABLE
AS RETURN

    SELECT 
        CONVERT(NVARCHAR(500),
                CONVERT(XML,
                        '<?xml version="1.0" encoding="UTF-8"?>' +
                        CONVERT(VARCHAR(500),
                                CONVERT(XML, @Base64EncodedUTF8String)
                                  .value('.','varbinary(max)')
                               )
                       )
               ) AS [DecodedValue];

GO

Et puis testez:

SELECT *
FROM   dbo.ConvertBase64EncodedUTF8ToUTF16LE(
          'T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=');
-- Où est le café le plus proche?

Utilisez simplement avec CROSS APPLY si vous effectuez une opération basée sur un ensemble.

9
Solomon Rutzky