Как разобрать запрос multipart/form-data в XHR с несколькими файлами

Я видел несколько примеров онлайновых парсеров multipart/form-data, но ни один из них не работает на моем сайте. Я использую элемент управления Kendo Upload, установленный в асинхронный режим, и с включенной пакетной загрузкой сгенерированный запрос выглядит примерно так:

BOUNDARY
Content-Disposition: form-data; name="Param_1"

Param_1 Value
BOUNDARY
Content-Disposition: form-data; name="Param_2"

Param_2 Value
BOUNDARY
Content-Disposition: form-data; name="files[]"; filename="A.docx"
Content-Type: application/octet-stream

[Binary Data Here]
BOUNDARY
Content-Disposition: form-data; name="files[]"; filename="B.docx"
Content-Type: application/octet-stream

[Binary Data Here]
BOUNDARY--

Каждая библиотека, которую я нашел в сети, успешно извлекает параметры и первый файл, но некоторые никогда не видят второй и те, которые сохраняют его неправильно. Существует аналогичный вопрос для данных SO WCF multipart / form с несколькими файлами, но это решение работает только для текстовых файлов, а не для двоичных файлов.

1 ответ

Решение

Проблема, с которой другие решения сталкивались с двоичными файлами, заключалась в том, что они преобразовывали ответ в строку для его анализа, а затем преобразовывали эту строку обратно в файл, это не работает с двоичными данными. Я придумал решение, где вместо того, чтобы превратить ответ в строку для его анализа, я оставил его как байт [] и разделил его на разделитель как байт [], используя код, найденный здесь. Как только это будет сделано, преобразуйте каждый фрагмент в строку, чтобы увидеть, является ли это параметром или файлом, если это параметр, затем прочитайте его, в противном случае запишите его как файл. Вот рабочий код, который предполагает, что у вас есть Stream в качестве байта [] и разделитель в виде строки:

// given byte[] streamByte, String delimiterString, and Encoding encoding
Regex regQuery;
Match regMatch;
string propertyType;

byte[] delimiterBytes = encoding.GetBytes(delimiterString);
byte[] delimiterWithNewLineBytes = encoding.GetBytes(delimiterString + "\r\n");
// the request ends DELIMITER--\r\n
byte[] delimiterEndBytes = encoding.GetBytes("\r\n" + delimiterString + "--\r\n");
int lengthDifferenceWithEndBytes = (delimiterString + "--\r\n").Length;

// seperate by delimiter + newline
// ByteArraySplit code found at https://stackru.com/a/9755250/4244411
byte[][] separatedStream = ByteArraySplit(streamBytes, delimiterWithNewLineBytes);
streamBytes = null;
for (int i = 0; i < separatedStream.Length; i++)
{
    // parse out whether this is a parameter or a file
    // get the first line of the byte[] as a string
    string thisPieceAsString = encoding.GetString(separatedStream[i]);

    if (string.IsNullOrWhiteSpace(thisPieceAsString)) { continue; }

    string firstLine = thisPieceAsString.Substring(0, thisPieceAsString.IndexOf("\r\n"));

    // Check the item to see what it is
    regQuery = new Regex(@"(?<=name\=\"")(.*?)(?=\"")");
    regMatch = regQuery.Match(firstLine);
    propertyType = regMatch.Value.Trim();

    // get the index of the start of the content and the end of the content
    int indexOfStartOfContent = thisPieceAsString.IndexOf("\r\n\r\n") + "\r\n\r\n".Length;

    // this line compares the name to the name of the html input control, 
    // this can be smarter by instead looking for the filename property
    if (propertyType != "files")
    {
        // this is a parameter!
        // if this is the last piece, chop off the final delimiter
        int lengthToRemove = (i == separatedStream.Length - 1) ? lengthDifferenceWithEndBytes : 0;
        string value = thisPieceAsString.Substring(indexOfStartOfContent, thisPieceAsString.Length - "\r\n".Length - indexOfStartOfContent - lengthToRemove);
        // do something with the parameter
    }
    else
    {
        // this is a file!
        regQuery = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
        regMatch = regQuery.Match(firstLine);
        string fileName = regMatch.Value.Trim();

        // get the content byte[]
        // if this is the last piece, chop off the final delimiter
        int lengthToRemove = (i == separatedStream.Length - 1) ? delimiterEndBytes.Length : 0;
        int contentByteArrayStartIndex = encoding.GetBytes(thisPieceAsString.Substring(0, indexOfStartOfContent)).Length;
        byte[] fileData = new byte[separatedStream[i].Length - contentByteArrayStartIndex - lengthToRemove];
        Array.Copy(separatedStream[i], contentByteArrayStartIndex, fileData, 0, separatedStream[i].Length - contentByteArrayStartIndex - lengthToRemove);
        // save the fileData byte[] as the file
    }

}
Другие вопросы по тегам