2017-02-22 4 views
0

J'essaie d'implémenter un POST à un service Web. Je dois envoyer un fichier dont le type est variable (.docx, .pdf, .txt) avec une chaîne au format JSON.Le téléchargement de fichier échoue, lors de la publication avec Indy et le nom de fichier contient des caractères grecs

je réussi à publier des fichiers avec succès avec un code similaire à ce qui suit:

procedure DoRequest; 
var 
    Http: TIdHTTP; 
    Params: TIdMultipartFormDataStream; 
    RequestStream, ResponseStream: TStringStream; 
    JRequest, JResponse: TJSONObject; 
    url: string; 
begin 
    url := 'some_custom_service' 

    JRequest := TJSONObject.Create; 
    JResponse := TJSONObject.Create; 
    try 
    JRequest.AddPair('Pair1', 'Value1'); 
    JRequest.AddPair('Pair2', 'Value2'); 
    JRequest.AddPair('Pair3', 'Value3'); 

    Http := TIdHTTP.Create(nil);   
    ResponseStream := TStringStream.Create; 
    RequestStream := TStringStream.Create(UTF8Encode(JRequest.ToString)); 
    try 
     Params := TIdMultipartFormDataStream.Create; 
     Params.AddFile('File', ceFileName.Text, '').ContentTransfer := ''; 
     Params.AddFormField('Json', 'application/json', '', RequestStream); 

     Http.Post(url, Params, ResponseStream); 
     JResponse := TJSONObject.ParseJSONValue(ResponseStream.DataString) as TJSONObject; 
    finally  
     RequestStream.Free; 
     ResponseStream.Free; 
     Params.Free; 
     Http.Free; 
    end; 
    finally 
    JRequest.Free; 
    JResponse.Free; 
    end; 
end; 

Le problème apparaît lorsque je tente d'envoyer un fichier qui contient des caractères grecs et des espaces dans le nom du fichier. Parfois, il échoue et parfois il réussit.

Après beaucoup de recherches, je remarque que l'en-tête POST est codée par classe d'Indy TIdFormDataField en utilisant la fonction EncodeHeader(). Lorsque le message échoue, le nom de fichier codé dans l'en-tête est divisé, par rapport à la publication réussie où n'est pas divisé.

Par exemple:

  • Επιστολή εκπαιδευτικο.docx est codé comme =?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#$D#$A' =?UTF-8?B?eA==?=, qui échoue.
  • Επιστολή εκπαιδευτικ.docx est codé comme =?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?=, ce qui réussit.
  • Επιστολή εκπαιδευτικ .docx est codé comme =?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx, ce qui échoue.

J'ai essayé de changer le codage du nom de fichier, le AContentType de la procédure AddFile() et le ContentTransfer, mais aucun de ceux changer le comportement, et je reçois encore des erreurs lorsque le nom de fichier codé est divisé.

Est-ce une sorte de bogue, ou ai-je oublié quelque chose?

Mon code fonctionne pour tous les cas sauf ceux que j'ai décrits ci-dessus. J'utilise Delphi XE3 avec Indy10.

Répondre

1

EncodeHeader() a quelques problèmes connus avec des chaînes Unicode:

EncodeHeader() needs to take codeunits into account when splitting data between adjacent encoded-words

Fondamentalement, un mot codé MIME ne peut pas être plus de 75 caractères, si longtemps texte est divisé. Mais lors de l'encodage d'une longue chaîne Unicode, n'importe quel caractère Unicode peut être codé en charset en utilisant 1 octet ou plus, et EncodeHeader() n'évite pas encore de diviser de façon erronée un caractère multi-octet entre deux octets individuels en mots codés séparés (ce qui est illégal et explicite interdit par RFC 2047 de la spécification MIME).

Cependant, ce n'est pas ce qui se passe dans vos exemples.

Dans votre premier exemple, 'Επιστολή εκπαιδευτικο.docx' est trop long pour être codé comme un seul mot MIME, de sorte qu'il devient divisé en 'Επιστολή εκπαιδευτικο.doc''x', qui sont sous-chaînes puis encodées séparément. Ce est légal dans MIME pour long texte (bien que vous pourriez avoir prévu Indy de scinder le texte en 'Επιστολή' au lieu ' εκπαιδευτικο.doc', ou même 'Επιστολή'' εκπαιδευτικο''.doc'.Cela pourrait être une possibilité dans une future version). Les mots MIME adjacents qui sont séparés par des espaces seulement sont destinés à être concaténés ensemble sans séparer les espaces lors du décodage, produisant ainsi de nouveau 'Επιστολή εκπαιδευτικο.docx'. Si le serveur ne le fait pas, il a un défaut dans son décodeur (peut-être décode-t-il 'Επιστολή εκπαιδευτικο.doc x' à la place?).

Dans votre second exemple, 'Επιστολή εκπαιδευτικ.docx' est suffisamment court pour être encodé en un seul mot MIME.

Dans votre troisième exemple, 'Επιστολή εκπαιδευτικ .docx' obtient split au second espaces (pas la première) dans 'Επιστολή εκπαιδευτικ'' .docx' sous-chaînes, et seule la première chaîne doit être codé. Ceci est légal dans MIME. Lorsqu'il est décodé, le texte décodé est destiné à être concaténé avec le texte non codé suivant, en préservant les espaces entre eux, produisant ainsi à nouveau 'Επιστολή εκπαιδευτικ .docx'. Si le serveur ne le fait pas, il a un défaut dans son décodeur (peut-être décode-t-il 'Επιστολή εκπαιδευτικ.docx' à la place?).

Si vous exécutez ces exemples les noms de fichiers par encodeur en-tête MIME/décodeur d'Indy, ils ne décodent pas correctement:

var 
    s: String; 
begin 
    s := EncodeHeader('Επιστολή εκπαιδευτικο.docx', '', 'B', 'UTF-8'); 
    ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#13#10' =?UTF-8?B?eA==?=' 
    s := DecodeHeader(s); 
    ShowMessage(s); // 'Επιστολή εκπαιδευτικο.docx' 

    s := EncodeHeader('Επιστολή εκπαιδευτικ.docx', '', 'B', 'UTF-8'); 
    ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?=' 
    s := DecodeHeader(s); 
    ShowMessage(s); // 'Επιστολή εκπαιδευτικ.docx' 

    s := EncodeHeader('Επιστολή εκπαιδευτικ .docx', '', 'B', 'UTF-8'); 
    ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx' 
    s := DecodeHeader(s); 
    ShowMessage(s); // 'Επιστολή εκπαιδευτικ .docx' 
end; 

Le problème semble être sur le décodage côté serveur, et non sur le codage côté client d'Indy.

Cela dit, si vous utilisez une version assez récente Indy 10 (novembre 2011 ou plus tard), TIdFormDataField possède une propriété HeaderEncoding, qui par défaut 'B' (base64) dans des environnements Unicode. Cependant, la logique de séparation affecte également 'Q' (cité imprimable) et, donc qui peuvent ou peuvent ne pas fonctionner pour vous, que ce soit (mais vous pouvez l'essayer):

with Params.AddFile('File', ceFileName.Text, '') do 
begin 
    ContentTransfer := ''; 
    HeaderEncoding := 'Q'; // <--- here 
    HeaderCharSet := 'utf-8'; 
end; 

Sinon, une solution de contournement est peut-être au changement la valeur à '8' (8 bits) à la place, ce qui désactive l'encodage MIME (mais pas charset encodage):

with Params.AddFile('File', ceFileName.Text, '') do 
begin 
    ContentTransfer := ''; 
    HeaderEncoding := '8'; // <--- here 
    HeaderCharSet := 'utf-8'; 
end; 

Notez simplement que si le serveur ne s'y attend pas UTF-8 octets bruts pour le nom de fichier, vous pourriez se heurtent toujours à des problèmes (par exemple, 'Επιστολή εκπαιδευτικο.docx' étant interprété comme 'Επιστολή εκπαιδευτικο.docx', par exemple).

+0

Merci beaucoup @Remy pour la réponse et toutes les explications. J'ai contacté le propriétaire du serveur et nous allons essayer de le déboguer ensemble. En attendant, j'ai essayé la deuxième solution de contournement (8 bits) et travaillé comme un charme. – stmpakir