/// <summary> /// Parses a section of the stream that is known to be parameter data. /// </summary> /// <param name="parameters"> /// The header parameters of this section. "name" must be a valid key. /// </param> /// <param name="reader"> /// The StreamReader to read the data from /// </param> /// <returns> /// The <see cref="ParameterPart" /> containing the parsed data (name, value). /// </returns> /// <exception cref="MultipartParseException"> /// thrown if unexpected data is found such as running out of stream before hitting the boundary. /// </exception> private void ParseParameterPart(Dictionary <string, string> parameters, RebufferableBinaryReader reader) { var data = new StringBuilder(); bool firstTime = true; string line = reader.ReadLine(); while (line != boundary && line != endBoundary) { if (line == null) { throw new MultipartParseException("Unexpected end of stream. Is there an end boundary?"); } if (firstTime) { data.Append(line); firstTime = false; } else { data.Append(Environment.NewLine); data.Append(line); } line = reader.ReadLine(); } if (line == endBoundary) { readEndBoundary = true; } var part = new ParameterPart(parameters["name"], data.ToString()); ParameterHandler(part); }
/// <summary> /// Detects the boundary from the input stream. Assumes that the /// current position of the reader is the start of the file and therefore /// the beginning of the boundary. /// </summary> /// <param name="reader"> /// The binary reader to parse /// </param> /// <returns> /// The boundary string /// </returns> private static string DetectBoundary(RebufferableBinaryReader reader) { string boundary = string.Concat(reader.ReadLine().Skip(2)); reader.Buffer("--" + boundary + "\n"); return(boundary); }
/// <summary> /// Begins executing the parser. This should be called after all handlers have been set. /// </summary> public void Run() { var reader = new RebufferableBinaryReader(stream, Encoding, BinaryBufferSize); if (boundary == null) { boundary = DetectBoundary(reader); } boundary = "--" + boundary; endBoundary = boundary + "--"; boundaryBinary = Encoding.GetBytes(boundary); endBoundaryBinary = Encoding.GetBytes(endBoundary); Debug.Assert(BinaryBufferSize >= endBoundaryBinary.Length, "binaryBufferSize must be bigger then the boundary"); Parse(reader); }
/// <summary> /// Parses the header of the next section of the multipart stream and /// determines if it contains file data or parameter data. /// </summary> /// <param name="reader"> /// The StreamReader to read data from. /// </param> /// <exception cref="MultipartParseException"> /// thrown if unexpected data is hit such as end of stream. /// </exception> private void ParseSection(RebufferableBinaryReader reader) { var parameters = new Dictionary <string, string>(); string line = reader.ReadLine(); while (line != string.Empty) { if (line == null) { throw new MultipartParseException("Unexpected end of stream"); } if (line == boundary || line == endBoundary) { throw new MultipartParseException("Unexpected end of section"); } Dictionary <string, string> values = SplitBySemicolonIgnoringSemicolonsInQuotes(line).Select(x => x.Split(new[] { ':', '=' }, 2)).Where(x => x.Length == 2).ToDictionary(x => x[0].Trim().Replace("\"", string.Empty).ToLower(), x => x[1].Trim().Replace("\"", string.Empty)); try { foreach (var pair in values) { parameters.Add(pair.Key, pair.Value); } } catch (ArgumentException) { throw new MultipartParseException("Duplicate field in section"); } line = reader.ReadLine(); } if (parameters.ContainsKey("filename")) { ParseFilePart(parameters, reader); } else { ParseParameterPart(parameters, reader); } }
/// <summary> /// Begins the parsing of the stream into objects. /// </summary> /// <param name="reader"> /// The multipart/form-data binary reader to parse from. /// </param> /// <exception cref="MultipartParseException"> /// thrown on finding unexpected data such as a boundary before we are ready for one. /// </exception> private void Parse(RebufferableBinaryReader reader) { while (true) { string line = reader.ReadLine(); if (line == boundary) { break; } if (line == null) { throw new MultipartParseException("Could not find expected boundary"); } } while (!readEndBoundary) { ParseSection(reader); } if (StreamClosedHandler != null) { StreamClosedHandler(); } }
/// <summary> /// Parses a section of the stream that is known to be file data. /// </summary> /// <param name="parameters"> /// The header parameters of this file, expects "name" and "filename" to be valid keys /// </param> /// <param name="reader"> /// The StreamReader to read the data from /// </param> /// <returns> /// The <see cref="FilePart" /> con[] taining the parsed data (name, filename, stream containing file). /// </returns> private void ParseFilePart(Dictionary <string, string> parameters, RebufferableBinaryReader reader) { string name = parameters["name"]; string filename = parameters["filename"]; string contentType = parameters.ContainsKey("content-type") ? parameters["content-type"] : "text/plain"; string contentDisposition = parameters.ContainsKey("content-disposition") ? parameters["content-disposition"] : "form-data"; var curBuffer = new byte[BinaryBufferSize]; var prevBuffer = new byte[BinaryBufferSize]; var fullBuffer = new byte[BinaryBufferSize * 2]; int curLength = 0; int prevLength = 0; int fullLength = 0; prevLength = reader.Read(prevBuffer, 0, prevBuffer.Length); do { curLength = reader.Read(curBuffer, 0, curBuffer.Length); Buffer.BlockCopy(prevBuffer, 0, fullBuffer, 0, prevLength); Buffer.BlockCopy(curBuffer, 0, fullBuffer, prevLength, curLength); fullLength = prevLength + curLength; int endBoundaryPos = SubsequenceFinder.Search(fullBuffer, endBoundaryBinary, fullLength); int endBoundaryLength = endBoundaryBinary.Length; int boundaryPos = SubsequenceFinder.Search(fullBuffer, boundaryBinary, fullLength); int boundaryLength = boundaryBinary.Length; if (boundaryPos + boundaryLength == fullLength) { boundaryPos = -1; } int endPos = -1; int endPosLength = 0; if (endBoundaryPos >= 0 && boundaryPos >= 0) { if (boundaryPos < endBoundaryPos) { endPos = boundaryPos; endPosLength = boundaryLength; } else { endPos = endBoundaryPos; endPosLength = endBoundaryLength; readEndBoundary = true; } } else if (boundaryPos >= 0 && endBoundaryPos < 0) { endPos = boundaryPos; endPosLength = boundaryLength; } else if (boundaryPos < 0 && endBoundaryPos >= 0) { endPos = endBoundaryPos; endPosLength = endBoundaryLength; readEndBoundary = true; } if (endPos != -1) { int boundaryNewlineOffset = CalculateNewlineLength(ref fullBuffer, Math.Min(fullLength - 1, endPos + endPosLength)); int maxNewlineBytes = Encoding.GetMaxByteCount(2); int bufferNewlineOffset = FindNextNewline(ref fullBuffer, Math.Max(0, endPos - maxNewlineBytes), maxNewlineBytes); int bufferNewlineLength = CalculateNewlineLength(ref fullBuffer, bufferNewlineOffset); FileHandler(name, filename, contentType, contentDisposition, fullBuffer, endPos - bufferNewlineLength); int writeBackOffset = endPos + endPosLength + boundaryNewlineOffset; int writeBackAmount = (prevLength + curLength) - writeBackOffset; var writeBackBuffer = new byte[writeBackAmount]; Buffer.BlockCopy(fullBuffer, writeBackOffset, writeBackBuffer, 0, writeBackAmount); reader.Buffer(writeBackBuffer); break; } FileHandler(name, filename, contentType, contentDisposition, prevBuffer, prevLength); byte[] tempBuffer = curBuffer; curBuffer = prevBuffer; prevBuffer = tempBuffer; prevLength = curLength; }while (prevLength != 0); }