/// <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) { // Presumably the boundary is --|||||||||||||| where -- is the stuff added on to // the front as per the protocol and ||||||||||||| is the part we care about. var boundary = string.Concat(reader.ReadLine().Skip(2)); reader.Buffer("--" + boundary + "\n"); return(boundary); }
/// <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"/> containing the parsed data (name, filename, stream containing file). /// </returns> private HttpFile ParseFilePart(Dictionary <string, string> parameters, RebufferableBinaryReader reader) { // We want to create a stream and fill it with the data from the // file. var temp = Security.TempFile; using (var data = new FileStream(temp, FileMode.OpenOrCreate)) { var curBuffer = new byte[BinaryBufferSize]; var prevBuffer = new byte[BinaryBufferSize]; int curLength = 0; int prevLength = 0; prevLength = reader.Read(prevBuffer, 0, prevBuffer.Length); do { curLength = reader.Read(curBuffer, 0, curBuffer.Length); // Combine both buffers into the fullBuffer // See: http://stackoverflow.com/questions/415291/best-way-to-combine-two-or-more-byte-arrays-in-c-sharp var fullBuffer = new byte[BinaryBufferSize * 2]; Buffer.BlockCopy(prevBuffer, 0, fullBuffer, 0, prevLength); Buffer.BlockCopy(curBuffer, 0, fullBuffer, prevLength, curLength); // Now we want to check for a substring within the current buffer. // We need to find the closest substring greedily. That is find the // closest boundary and don't miss the end --'s if it's an end boundary. int endBoundaryPos = SubsequenceFinder.Search(fullBuffer, this.endBoundaryBinary); int endBoundaryLength = this.endBoundaryBinary.Length; int boundaryPos = SubsequenceFinder.Search(fullBuffer, this.boundaryBinary); int boundaryLength = this.boundaryBinary.Length; // We need to select the appropriate position and length // based on the smallest non-negative position. int endPos = -1; int endPosLength = 0; if (endBoundaryPos >= 0 && boundaryPos >= 0) { if (boundaryPos < endBoundaryPos) { // Select boundary endPos = boundaryPos; endPosLength = boundaryLength; } else { // Select end boundary endPos = endBoundaryPos; endPosLength = endBoundaryLength; this.readEndBoundary = true; } } else if (boundaryPos >= 0 && endBoundaryPos < 0) { // Select boundary endPos = boundaryPos; endPosLength = boundaryLength; } else if (boundaryPos < 0 && endBoundaryPos >= 0) { // Select end boundary endPos = endBoundaryPos; endPosLength = endBoundaryLength; this.readEndBoundary = true; } if (endPos != -1) { // Now we need to check if the endPos is followed by \r\n or just \n. HTTP // specifies \r\n but some clients might encode with \n. Or we might get 0 if // we are at the end of the file. int boundaryNewlineOffset = CalculateNewlineLength(ref fullBuffer, endPos + endPosLength); // We also need to check if the last n characters of the buffer to write // are a newline and if they are ignore them. var maxNewlineBytes = Encoding.GetMaxByteCount(2); int bufferNewlineOffset = this.FindNextNewline(ref fullBuffer, Math.Max(0, endPos - maxNewlineBytes), maxNewlineBytes); int bufferNewlineLength = this.CalculateNewlineLength(ref fullBuffer, bufferNewlineOffset); // We've found an end. We need to consume all the binary up to it // and then write the remainder back to the original stream. Then we // need to modify the original streams position to take into account // the new data. // We also want to chop off the newline that is inserted by the protocl. // We can do this by reducing endPos by the length of newline in this environment // and encoding data.Write(fullBuffer, 0, 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); // stream.Write(fullBuffer, writeBackOffset, writeBackAmount); // stream.Position = stream.Position - writeBackAmount; // stream.Flush(); data.Position = 0; data.Flush(); break; } // No end, consume the entire previous buffer data.Write(prevBuffer, 0, prevLength); data.Flush(); // Now we want to swap the two buffers, we don't care // what happens to the data from prevBuffer so we set // curBuffer to it so it gets overwrited. byte[] tempBuffer = curBuffer; curBuffer = prevBuffer; prevBuffer = tempBuffer; // We don't need to swap the lengths because // curLength will be overwritten in the next // iteration of the loop. prevLength = curLength; }while (prevLength != 0); data.Flush(); data.Close(); data.Dispose(); } var contentType = parameters.ContainsKey("content-type") ? parameters["content-type"] : "text/plain"; var contentDisposition = parameters.ContainsKey("content-disposition") ? parameters["content-disposition"] : "form-data"; var part = new HttpFile() { Name = parameters["name"], FileName = parameters["filename"], Extension = parameters["filename"].Contains('.') ? parameters["filename"].Substring(parameters["filename"].LastIndexOf('.')) : "", TempFile = temp, ContentType = contentType, ContentDisposition = contentDisposition }; return(part); }