e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
317 lines
9.9 KiB
C#
317 lines
9.9 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="MultipartContentParser.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Multipart content parser.
|
|
*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*/
|
|
|
|
namespace System.Web {
|
|
using System.Text;
|
|
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using System.Web.Util;
|
|
|
|
/*
|
|
* Element of the multipart content
|
|
*/
|
|
internal sealed class MultipartContentElement {
|
|
private String _name;
|
|
private String _filename;
|
|
private String _contentType;
|
|
private HttpRawUploadedContent _data;
|
|
private int _offset;
|
|
private int _length;
|
|
|
|
internal MultipartContentElement(String name, String filename, String contentType, HttpRawUploadedContent data, int offset, int length) {
|
|
_name = name;
|
|
_filename = filename;
|
|
_contentType = contentType;
|
|
_data = data;
|
|
_offset = offset;
|
|
_length = length;
|
|
}
|
|
|
|
internal bool IsFile {
|
|
get { return(_filename != null);}
|
|
}
|
|
|
|
internal bool IsFormItem {
|
|
get { return(_filename == null);}
|
|
}
|
|
|
|
internal String Name {
|
|
get { return _name;}
|
|
}
|
|
|
|
internal HttpPostedFile GetAsPostedFile() {
|
|
return new HttpPostedFile(
|
|
_filename,
|
|
_contentType,
|
|
new HttpInputStream(_data, _offset, _length));
|
|
}
|
|
|
|
internal String GetAsString(Encoding encoding) {
|
|
if (_length > 0) {
|
|
return encoding.GetString(_data.GetAsByteArray(_offset, _length));
|
|
}
|
|
else {
|
|
return String.Empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Multipart content parser. Split content into elements.
|
|
*/
|
|
internal sealed class HttpMultipartContentTemplateParser {
|
|
private HttpRawUploadedContent _data;
|
|
private int _length;
|
|
|
|
private int _pos;
|
|
private ArrayList _elements = new ArrayList();
|
|
|
|
// currently parsed line
|
|
private int _lineStart = -1;
|
|
private int _lineLength = -1;
|
|
|
|
// last boundary has extra --
|
|
private bool _lastBoundaryFound;
|
|
|
|
// part separator
|
|
private byte[] _boundary;
|
|
|
|
// current header values
|
|
private String _partName;
|
|
private String _partFilename;
|
|
private String _partContentType;
|
|
|
|
// current part's content data
|
|
private int _partDataStart = -1;
|
|
private int _partDataLength = -1;
|
|
|
|
// encoding
|
|
private Encoding _encoding;
|
|
|
|
|
|
private HttpMultipartContentTemplateParser(HttpRawUploadedContent data, int length, byte[] boundary, Encoding encoding) {
|
|
_data = data;
|
|
_length = length;
|
|
_boundary = boundary;
|
|
_encoding = encoding;
|
|
}
|
|
|
|
private bool AtEndOfData() {
|
|
return(_pos >= _length || _lastBoundaryFound);
|
|
}
|
|
|
|
private bool GetNextLine() {
|
|
int i = _pos;
|
|
|
|
_lineStart = -1;
|
|
|
|
while (i < _length) {
|
|
if (_data[i] == 10) { // '\n'
|
|
_lineStart = _pos;
|
|
_lineLength = i - _pos;
|
|
_pos = i+1;
|
|
|
|
// ignore \r
|
|
if (_lineLength > 0 && _data[i-1] == 13)
|
|
_lineLength--;
|
|
|
|
// line found
|
|
break;
|
|
}
|
|
|
|
if (++i == _length) {
|
|
// last line doesn't end with \n
|
|
_lineStart = _pos;
|
|
_lineLength = i - _pos;
|
|
_pos = _length;
|
|
}
|
|
}
|
|
|
|
return(_lineStart >= 0);
|
|
}
|
|
|
|
private String ExtractValueFromContentDispositionHeader(String l, int pos, String name) {
|
|
String pattern = " " + name + "=";
|
|
int i1 = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, pattern, pos, CompareOptions.IgnoreCase);
|
|
if (i1 < 0) {
|
|
pattern = ";" + name + "=";
|
|
i1 = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, pattern, pos, CompareOptions.IgnoreCase);
|
|
if (i1 < 0) {
|
|
pattern = name + "=";
|
|
i1 = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, pattern, pos, CompareOptions.IgnoreCase);
|
|
}
|
|
}
|
|
if (i1 < 0)
|
|
return null;
|
|
i1 += pattern.Length;
|
|
if (i1 >= l.Length)
|
|
return String.Empty;
|
|
|
|
if (l[i1] == '"') {
|
|
i1 += 1;
|
|
int i2 = l.IndexOf('"', i1);
|
|
if (i2 < 0)
|
|
return null;
|
|
if (i2 == i1)
|
|
return String.Empty;
|
|
|
|
return l.Substring(i1, i2-i1);
|
|
}
|
|
else {
|
|
int i2 = l.IndexOf(';', i1);
|
|
if (i2 < 0)
|
|
i2 = l.Length;
|
|
|
|
return l.Substring(i1, i2-i1).Trim();
|
|
}
|
|
}
|
|
|
|
private void ParsePartHeaders() {
|
|
_partName = null;
|
|
_partFilename = null;
|
|
_partContentType = null;
|
|
|
|
while (GetNextLine()) {
|
|
if (_lineLength == 0)
|
|
break; // empty line signals end of headers
|
|
|
|
// get line as String
|
|
byte[] lineBytes = new byte[_lineLength];
|
|
_data.CopyBytes(_lineStart, lineBytes, 0, _lineLength);
|
|
String line = _encoding.GetString(lineBytes);
|
|
|
|
// parse into header and value
|
|
int ic = line.IndexOf(':');
|
|
if (ic < 0)
|
|
continue; // not a header
|
|
|
|
// remeber header
|
|
String header = line.Substring(0, ic);
|
|
|
|
if (StringUtil.EqualsIgnoreCase(header, "Content-Disposition")) {
|
|
// parse name and filename
|
|
_partName = ExtractValueFromContentDispositionHeader(line, ic+1, "name");
|
|
_partFilename = ExtractValueFromContentDispositionHeader(line, ic+1, "filename");
|
|
}
|
|
else if (StringUtil.EqualsIgnoreCase(header, "Content-Type")) {
|
|
_partContentType = line.Substring(ic+1).Trim();
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool AtBoundaryLine() {
|
|
// check for either regular or last boundary line length
|
|
|
|
int len = _boundary.Length;
|
|
|
|
if (_lineLength != len && _lineLength != len+2)
|
|
return false;
|
|
|
|
// match with boundary
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
if (_data[_lineStart+i] != _boundary[i])
|
|
return false;
|
|
}
|
|
|
|
// regular boundary line?
|
|
|
|
if (_lineLength == len)
|
|
return true;
|
|
|
|
// last boundary line? (has to end with "--")
|
|
|
|
if (_data[_lineStart+len] != 45 || _data[_lineStart+len+1] != 45)
|
|
return false;
|
|
|
|
_lastBoundaryFound = true; // remember that it is last
|
|
return true;
|
|
}
|
|
|
|
private void ParsePartData() {
|
|
_partDataStart = _pos;
|
|
_partDataLength = -1;
|
|
|
|
while (GetNextLine()) {
|
|
if (AtBoundaryLine()) {
|
|
// calc length: adjust to exclude [\r]\n before the separator
|
|
int iEnd = _lineStart - 1;
|
|
if (_data[iEnd] == 10) // \n
|
|
iEnd--;
|
|
if (_data[iEnd] == 13) // \r
|
|
iEnd--;
|
|
|
|
_partDataLength = iEnd - _partDataStart + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ParseIntoElementList() {
|
|
//
|
|
// Skip until first boundary
|
|
//
|
|
|
|
while (GetNextLine()) {
|
|
if (AtBoundaryLine())
|
|
break;
|
|
}
|
|
|
|
if (AtEndOfData())
|
|
return;
|
|
|
|
//
|
|
// Parse the parts
|
|
//
|
|
|
|
do {
|
|
// Parse current part's headers
|
|
|
|
ParsePartHeaders();
|
|
|
|
if (AtEndOfData())
|
|
break; // cannot stop after headers
|
|
|
|
// Parse current part's data
|
|
|
|
ParsePartData();
|
|
|
|
if (_partDataLength == -1)
|
|
break; // ending boundary not found
|
|
|
|
// Remember the current part (if named)
|
|
|
|
if (_partName != null) {
|
|
_elements.Add(new MultipartContentElement(
|
|
_partName,
|
|
_partFilename,
|
|
_partContentType,
|
|
_data,
|
|
_partDataStart,
|
|
_partDataLength));
|
|
}
|
|
}
|
|
while (!AtEndOfData());
|
|
}
|
|
|
|
/*
|
|
* Static method to do the parsing
|
|
*/
|
|
internal static MultipartContentElement[] Parse(HttpRawUploadedContent data, int length, byte[] boundary, Encoding encoding) {
|
|
HttpMultipartContentTemplateParser parser = new HttpMultipartContentTemplateParser(data, length, boundary, encoding);
|
|
parser.ParseIntoElementList();
|
|
return (MultipartContentElement[])parser._elements.ToArray(typeof(MultipartContentElement));
|
|
}
|
|
}
|
|
}
|