2017-02-22 10 views
0

Я пытаюсь внедрить POST в веб-службу. Мне нужно отправить файл, тип которого является переменным (.docx, .pdf, .txt) вместе с форматированной строкой JSON.Загрузка файла не удалась, когда публикация с Indy и имя файла содержит греческие символы

я смог успешно размещать файлы с кодом, подобную следующей:

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; 

Проблема возникает, когда я пытаюсь отправить файл, который содержит греческие символы и пробелы в имени файла. Иногда это терпит неудачу, и иногда это удается.

После большого количества исследований я замечаю, что заголовок POST кодируется классом Indys TIdFormDataField с использованием функции EncodeHeader(). Когда сообщение выходит из строя, закодированное имя файла в заголовке разделяется по сравнению с успешным сообщением, где не разделяется.

Например:

  • Επιστολή εκπαιδευτικο.docx кодируется как =?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#$D#$A' =?UTF-8?B?eA==?=, который выходит из строя.
  • Επιστολή εκπαιδευτικ.docx закодирован как =?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?=, который преуспевает.
  • Επιστολή εκπαιδευτικ .docx кодируется как =?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx, что не работает.

Я пытался изменить кодировку файла, тем AContentType из AddFile() процедуры и ContentTransfer, но ни один из них не изменят свое поведение, и я все еще получаю ошибки при кодированном файле расщепляется.

Это какая-то ошибка, или я чего-то не хватает?

Мой код работает для каждого случая, за исключением описанных выше.

Я использую Delphi XE3 с Indy10.

ответ

1

EncodeHeader() есть некоторые известные проблемы со строками Unicode:

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

В основном, MIME-закодирован слово не может быть более 75 символов в длину, так что длинный текст разбивается на. Но при кодировании длинной строки Unicode любой заданный символ Юникода может кодироваться кодировкой с использованием 1 или более байтов, а EncodeHeader() еще не избегает ошибочного разделения многобайтового символа между двумя отдельными байтами на отдельные закодированные слова (что является незаконным и явно запрещено RFC 2047 спецификации MIME).

Однако, это не то, что происходит в ваших примерах.

В первом примере, 'Επιστολή εκπαιδευτικο.docx' слишком долго, чтобы быть закодирована в виде одного MIME слова, поэтому он разбивается на 'Επιστολή εκπαιδευτικο.doc''x' подстроки, которые затем закодированы отдельно. Это законно в MIME для длинного текста (хотя вы, возможно, ожидали, что Indy разделит текст на 'Επιστολή'' εκπαιδευτικο.doc' или даже 'Επιστολή'' εκπαιδευτικο''.doc'.Это может быть возможностью в будущем выпуске). Смежные MIME-слова, которые разделены только пробелами, должны быть объединены вместе, не разделяя пробелы при декодировании, тем самым снова производя 'Επιστολή εκπαιδευτικο.docx'. Если сервер не делает этого, у него есть недостаток в его декодере (возможно, он декодируется как 'Επιστολή εκπαιδευτικο.doc x' вместо?).

В вашем втором примере 'Επιστολή εκπαιδευτικ.docx' достаточно короткий, чтобы быть закодированным как одно слово MIME.

В вашем третьем примере 'Επιστολή εκπαιδευτικ .docx' разбивается на второй пробел (не первый) в 'Επιστολή εκπαιδευτικ'' .docx' подстрок, и только первый подстроки должен быть закодирован. Это законно в MIME. При декодировании декодированный текст предназначен для конкатенации со следующим некодированным текстом, сохраняя пробел между ними, таким образом, снова создавая 'Επιστολή εκπαιδευτικ .docx'. Если сервер этого не делает, у него есть недостаток в его декодере (возможно, он декодируется как 'Επιστολή εκπαιδευτικ.docx' вместо?).

Если запустить эти примеры имен файлов через Инди MIME заголовка кодера/декодера, они декодировать правильно:

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; 

Таким образом, проблема, кажется, на стороне сервера декодирования, а не на стороне клиента кодирования Инди.

Это, как говорится, если вы используете достаточно свежую версию Indy 10 (ноябрь 2011 или более поздней версии), TIdFormDataField имеет HeaderEncoding свойство, которое по умолчанию 'B' (base64) в среде Unicode. Однако логика расщепления также влияет 'Q' (цит-печати), а также, так что может или не может работать для вас, либо (но вы можете попробовать его):

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

В противном случае, обходной путь может быть для изменения значение для '8' (8-бит), а не, что эффективно отключает кодирование MIME (но не кодировка кодировки):

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

Сразу отметим, что если сервер не ожидает сырые UTF-8 байт для имени файла, вы могли бы все еще сталкиваются с проблемами (например, 'Επιστολή εκπαιδευτικο.docx' интерпретируется как 'Επιστολή εκπαιδευτικο.docx', например).

+0

Большое спасибо @Remy за ответ и все объяснения. Я связался с владельцем сервера, и мы попытаемся отладить его вместе. Между тем я пробовал второй обход (8-бит) и работал как шарм. – stmpakir

Смежные вопросы