/// <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) { // Parsing references include: // RFC1341 section 7: http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html // RFC2388: http://www.ietf.org/rfc/rfc2388.txt // First we need to read untill we find a boundary while (true) { string line = reader.ReadLine(); if (line == this.boundary) { break; } if (line == null) { throw new MultipartParseException("Could not find expected boundary"); } } // Now that we've found the initial boundary we know where to start. // We need parse each individual section while (!this.readEndBoundary) { // ParseSection will parse up to and including // the next boundary. this.ParseSection(reader); } }
/// <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 we don't know the boundary now is the time to calculate it. if (boundary == null) { boundary = DetectBoundary(reader); } // It's important to remember that the boundary given in the header has a -- appended to the start // and the last one has a -- appended to the end boundary = "--" + boundary; endBoundary = boundary + "--"; // We add newline here because unlike reader.ReadLine() binary reading // does not automatically consume the newline, we want to add it to our signature // so we can automatically detect and consume newlines after the boundary boundaryBinary = Encoding.GetBytes(boundary); endBoundaryBinary = Encoding.GetBytes(endBoundary); Debug.Assert( BinaryBufferSize >= endBoundaryBinary.Length, "binaryBufferSize must be bigger then the boundary"); Parse(reader); }
/// <summary> /// Initializes a new instance of the <see cref="MultipartFormDataParser"/> class /// with the boundary, stream, input encoding and buffer size. /// </summary> /// <param name="stream"> /// The stream containing the multipart data /// </param> /// <param name="boundary"> /// The multipart/form-data boundary. This should be the value /// returned by the request header. /// </param> /// <param name="encoding"> /// The encoding of the multipart data /// </param> /// <param name="binaryBufferSize"> /// The size of the buffer to use for parsing the multipart form data. This must be larger /// then (size of boundary + 4 + # bytes in newline). /// </param> public MultipartFormDataParser(Stream stream, string boundary, Encoding encoding, int binaryBufferSize) { this.Parameters = new Dictionary <string, ParameterPart>(); this.Files = new List <FilePart>(); this.Encoding = encoding; this.BinaryBufferSize = binaryBufferSize; this.readEndBoundary = false; using (var reader = new RebufferableBinaryReader(stream, this.Encoding, this.BinaryBufferSize)) { // If we don't know the boundary now is the time to calculate it. if (boundary == null) { boundary = DetectBoundary(reader); } // It's important to remember that the boundary given in the header has a -- appended to the start // and the last one has a -- appended to the end this.boundary = "--" + boundary; this.endBoundary = this.boundary + "--"; // We add newline here because unlike reader.ReadLine() binary reading // does not automatically consume the newline, we want to add it to our signature // so we can automatically detect and consume newlines after the boundary this.boundaryBinary = this.Encoding.GetBytes(this.boundary); this.endBoundaryBinary = this.Encoding.GetBytes(this.endBoundary); Debug.Assert( binaryBufferSize >= this.endBoundaryBinary.Length, "binaryBufferSize must be bigger then the boundary"); this.Parse(reader); } }
public void CanReadSingleCharacterBuffer() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("abc"), Encoding.UTF8); Assert.AreEqual(reader.Read(), 'a'); Assert.AreEqual(reader.Read(), 'b'); Assert.AreEqual(reader.Read(), 'c'); }
/// <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); }
public void CanReadMixedAsciiAndUTF8() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("5èats"), Encoding.UTF8); var buffer = new byte[Encoding.UTF8.GetByteCount("5èats")]; reader.Read(buffer, 0, buffer.Length); string result = Encoding.UTF8.GetString(buffer); Assert.AreEqual(result, "5èats"); }
public void CanReadAcrossMultipleBuffers() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("ars"), Encoding.UTF8); reader.Buffer(TestUtil.StringToByteNoBom("6ch")); var buffer = new byte[6]; reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "6chars"); }
public void CanReadSingleBuffer() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("6chars"), Encoding.UTF8); var buffer = new byte[Encoding.UTF8.GetByteCount("6chars")]; reader.Read(buffer, 0, buffer.Length); var result = Encoding.UTF8.GetString(buffer); Assert.AreEqual(result, "6chars"); }
public void CanReadByteLineOnMixedAsciiAndUTF8Text() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("Bonjour poignée"), Encoding.UTF8); byte[] bytes = reader.ReadByteLine(); var expected = new byte[] {66, 111, 110, 106, 111, 117, 114, 32, 112, 111, 105, 103, 110, 195, 169, 101}; foreach (var pair in expected.Zip(bytes, Tuple.Create)) { Assert.AreEqual(pair.Item1, pair.Item2); } }
public void CanReadMixedAsciiAndUTFCharacters() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("abcdèfg"), Encoding.UTF8); Assert.AreEqual(reader.Read(), 'a'); Assert.AreEqual(reader.Read(), 'b'); Assert.AreEqual(reader.Read(), 'c'); Assert.AreEqual(reader.Read(), 'd'); Assert.AreEqual(reader.Read(), 'è'); Assert.AreEqual(reader.Read(), 'f'); Assert.AreEqual(reader.Read(), 'g'); }
/// <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 LogPath = @"C://MYSITE/LOG/"; string strConn = "Data Source=FILIPPO-PC;Initial Catalog=PLCCS_DB;Integrated Security=True"; DatabaseManagement db = new DatabaseManagement(strConn, LogPath); //db.NewErrorLog("Reader : "+reader, DateTime.Now); // Presumably the boundary is --|||||||||||||| where -- is the stuff added on to // the front as per the protocol and ||||||||||||| is the part we care about. string boundary = string.Concat(reader.ReadLine().Skip(2)); reader.Buffer("--" + boundary + "\n"); //db.NewErrorLog("Boundary: "+boundary, DateTime.Now); return(boundary); }
/// <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) { // Our job is to get the actual "data" part of the parameter and construct // an actual ParameterPart object with it. All we need to do is read data into a string // untill we hit the boundary var data = new StringBuilder(); MemoryStream byteData = new MemoryStream(); bool firstTime = true; byte[] lineBytes = reader.ReadByteLine(); string line = Encoding.UTF8.GetString(lineBytes); while (line != boundary && line != endBoundary) { if (line == null) { throw new MultipartParseException("Unexpected end of stream. Is there an end boundary?"); } if (firstTime) { byteData.Write(lineBytes, 0, lineBytes.Length); data.Append(line); firstTime = false; } else { byteData.Write(lineBytes, 0, lineBytes.Length); data.Append(Environment.NewLine); data.Append(line); } line = reader.ReadLine(); } if (line == endBoundary) { readEndBoundary = true; } // If we're here we've hit the boundary and have the data! byteData.Position = 0; var part = new ParameterPart(parameters["name"], data.ToString(), byteData); ParameterHandler(part); }
/// <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) { // Our job is to get the actual "data" part of the parameter and construct // an actual ParameterPart object with it. All we need to do is read data into a string // untill we hit the boundary 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 section"); } if (firstTime) { data.Append(line); firstTime = false; } else { data.Append(Environment.NewLine); data.Append(line); } line = reader.ReadLine(); } if (line == endBoundary) { readEndBoundary = true; } // If we're here we've hit the boundary and have the data! var part = new ParameterPart(parameters["name"], data.ToString()); ParameterHandler(part); }
public void ReadCorrectlyHandlesLargerBufferThenStream() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("6chars"), Encoding.UTF8); var buffer = new byte[10]; int amountRead = reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "6chars\0\0\0\0"); Assert.AreEqual(amountRead, 6); }
public void ReadCanResumeInterruptedStream() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("6chars"), Encoding.UTF8); var buffer = new byte[4]; int amountRead = reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "6cha"); Assert.AreEqual(amountRead, 4); reader.Buffer(TestUtil.StringToByteNoBom("14intermission")); buffer = new byte[14]; amountRead = reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "14intermission"); Assert.AreEqual(amountRead, 14); buffer = new byte[2]; amountRead = reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "rs"); Assert.AreEqual(amountRead, 2); }
/// <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) { // Our first job is to determine what type of section this is: form data or file. // This is a bit tricky because files can still be encoded with Content-Disposition: form-data // in the case of single file uploads. Multi-file uploads have Content-Disposition: file according // to the spec however in practise it seems that multiple files will be represented by // multiple Content-Disposition: form-data files. 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"); } // This line parses the header values into a set of key/value pairs. For example: // Content-Disposition: form-data; name="textdata" // ["content-disposition"] = "form-data" // ["name"] = "textdata" // Content-Disposition: form-data; name="file"; filename="data.txt" // ["content-disposition"] = "form-data" // ["name"] = "file" // ["filename"] = "data.txt" // Content-Type: text/plain // ["content-type"] = "text/plain" Dictionary<string, string> values = SplitBySemicolonIgnoringSemicolonsInQuotes(line) .Select(x => x.Split(new[] {':', '='}, 2)) // Limit split to 2 splits so we don't accidently split characters in file paths. .ToDictionary( x => x[0].Trim().Replace("\"", string.Empty).ToLower(), x => x[1].Trim().Replace("\"", string.Empty)); // Here we just want to push all the values that we just retrieved into the // parameters dictionary. try { foreach (var pair in values) { parameters.Add(pair.Key, pair.Value); } } catch (ArgumentException) { throw new MultipartParseException("Duplicate field in section"); } line = reader.ReadLine(); } // Now that we've consumed all the parameters we're up to the body. We're going to do // different things depending on if we're parsing a, relatively small, form value or a // potentially large file. if (parameters.ContainsKey("filename")) { // Right now we assume that if a section contains filename then it is a file. // This assumption needs to be checked, it holds true in firefox but is untested for other // browsers. ParseFilePart(parameters, reader); } else { ParseParameterPart(parameters, reader); } }
/// <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) { // Our job is to get the actual "data" part of the parameter and construct // an actual ParameterPart object with it. All we need to do is read data into a string // untill we hit the boundary 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; } // If we're here we've hit the boundary and have the data! var part = new ParameterPart(parameters["name"], data.ToString()); ParameterHandler(part); }
/// <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"; // We want to create a stream and fill it with the data from the // file. 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); // Combine both buffers into the fullBuffer // See: http://stackoverflow.com/questions/415291/best-way-to-combine-two-or-more-byte-arrays-in-c-sharp Buffer.BlockCopy(prevBuffer, 0, fullBuffer, 0, prevLength); Buffer.BlockCopy(curBuffer, 0, fullBuffer, prevLength, curLength); fullLength = 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, endBoundaryBinary, fullLength); int endBoundaryLength = endBoundaryBinary.Length; int boundaryPos = SubsequenceFinder.Search(fullBuffer, boundaryBinary, fullLength); int boundaryLength = 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; 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; 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, Math.Min(fullLength - 1, 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. int maxNewlineBytes = Encoding.GetMaxByteCount(2); int bufferNewlineOffset = FindNextNewline( ref fullBuffer, Math.Max(0, endPos - maxNewlineBytes), maxNewlineBytes); int bufferNewlineLength = 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 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; } // No end, consume the entire previous buffer FileHandler(name, filename, contentType, contentDisposition, prevBuffer, prevLength); // 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); }
public void CanReadSingleCharacterOverBuffers() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("def"), Encoding.UTF8); reader.Buffer(TestUtil.StringToByteNoBom("abc")); Assert.Equal(reader.Read(), 'a'); Assert.Equal(reader.Read(), 'b'); Assert.Equal(reader.Read(), 'c'); Assert.Equal(reader.Read(), 'd'); Assert.Equal(reader.Read(), 'e'); Assert.Equal(reader.Read(), 'f'); }
/// <summary> /// Initializes a new instance of the <see cref="MultipartFormDataParser"/> class /// with the boundary, stream, input encoding and buffer size. /// </summary> /// <param name="stream"> /// The stream containing the multipart data /// </param> /// <param name="boundary"> /// The multipart/form-data boundary. This should be the value /// returned by the request header. /// </param> /// <param name="encoding"> /// The encoding of the multipart data /// </param> /// <param name="binaryBufferSize"> /// The size of the buffer to use for parsing the multipart form data. This must be larger /// then (size of boundary + 4 + # bytes in newline). /// </param> public MultipartFormDataParser(Stream stream, string boundary, Encoding encoding, int binaryBufferSize) { this.Parameters = new Dictionary<string, ParameterPart>(); this.Files = new List<FilePart>(); this.Encoding = encoding; this.BinaryBufferSize = binaryBufferSize; this.readEndBoundary = false; using (var reader = new RebufferableBinaryReader(stream, this.Encoding, this.BinaryBufferSize)) { // If we don't know the boundary now is the time to calculate it. if (boundary == null) { boundary = DetectBoundary(reader); } // It's important to remember that the boundary given in the header has a -- appended to the start // and the last one has a -- appended to the end this.boundary = "--" + boundary; this.endBoundary = this.boundary + "--"; // We add newline here because unlike reader.ReadLine() binary reading // does not automatically consume the newline, we want to add it to our signature // so we can automatically detect and consume newlines after the boundary this.boundaryBinary = this.Encoding.GetBytes(this.boundary); this.endBoundaryBinary = this.Encoding.GetBytes(this.endBoundary); Debug.Assert( binaryBufferSize >= this.endBoundaryBinary.Length, "binaryBufferSize must be bigger then the boundary"); this.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) { // Our first job is to determine what type of section this is: form data or file. // This is a bit tricky because files can still be encoded with Content-Disposition: form-data // in the case of single file uploads. Multi-file uploads have Content-Disposition: file according // to the spec however in practise it seems that multiple files will be represented by // multiple Content-Disposition: form-data files. 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"); } // This line parses the header values into a set of key/value pairs. For example: // Content-Disposition: form-data; name="textdata" // ["content-disposition"] = "form-data" // ["name"] = "textdata" // Content-Disposition: form-data; name="file"; filename="data.txt" // ["content-disposition"] = "form-data" // ["name"] = "file" // ["filename"] = "data.txt" // Content-Type: text/plain // ["content-type"] = "text/plain" Dictionary <string, string> values = SplitBySemicolonIgnoringSemicolonsInQuotes(line) .Select(x => x.Split(new[] { ':', '=' }, 2)) // select where the length of the array is equal to two, that way if it is only one it will // be ignored as it is invalid key-pair .Where(x => x.Length == 2) // Limit split to 2 splits so we don't accidently split characters in file paths. .ToDictionary( x => x[0].Trim().Replace("\"", string.Empty).ToLower(), x => x[1].Trim().Replace("\"", string.Empty)); // Here we just want to push all the values that we just retrieved into the // parameters dictionary. try { foreach (var pair in values) { parameters.Add(pair.Key, pair.Value); } } catch (ArgumentException) { throw new MultipartParseException("Duplicate field in section"); } line = reader.ReadLine(); } // Parser expects name parameter to always present, but it's not always the case // https://github.com/Vodurden/Http-Multipart-Data-Parser/issues/58 if (!parameters.ContainsKey("name")) { parameters.Add("name", Guid.NewGuid().ToString("N")); } // Now that we've consumed all the parameters we're up to the body. We're going to do // different things depending on if we're parsing a, relatively small, form value or a // potentially large file. if (parameters.ContainsKey("filename")) { // Right now we assume that if a section contains filename then it is a file. // This assumption needs to be checked, it holds true in firefox but is untested for other // browsers. ParseFilePart(parameters, reader); } else if (parameters.ContainsKey("content-type") && parameters["content-type"].Contains("octet-stream")) { // Posible issue: presens of filename is a wrong assumption, I guess more reliable is to check content type parameters.Add("filename", "na"); ParseFilePart(parameters, reader); } else { ParseParameterPart(parameters, reader); } }
/// <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 ParameterPart ParseParameterPart(Dictionary <string, string> parameters, RebufferableBinaryReader reader) { // Our job is to get the actual "data" part of the parameter and construct // an actual ParameterPart object with it. All we need to do is read data into a string // untill we hit the boundary var data = new StringBuilder(); string line = reader.ReadLine(); while (line != this.boundary && line != this.endBoundary) { if (line == null) { throw new MultipartParseException("Unexpected end of section"); } data.Append(line); line = reader.ReadLine(); } if (line == this.endBoundary) { this.readEndBoundary = true; } // If we're here we've hit the boundary and have the data! var part = new ParameterPart(parameters["name"], data.ToString()); return(part); }
public void ReadCorrectlyHandlesSmallerBufferThenStream() { var reader = new RebufferableBinaryReader(TestUtil.StringToStreamNoBom("6chars"), Encoding.UTF8); var buffer = new byte[4]; reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "6cha"); buffer = new byte[2]; reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "rs"); }
/// <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. string boundary = string.Concat(reader.ReadLine().Skip(2)); reader.Buffer("--" + boundary + "\n"); return boundary; }
public void ReadReturnsZeroOnNoData() { var reader = new RebufferableBinaryReader(new MemoryStream(), Encoding.UTF8); var buffer = new byte[6]; int amountRead = reader.Read(buffer, 0, buffer.Length); Assert.AreEqual(Encoding.UTF8.GetString(buffer), "\0\0\0\0\0\0"); Assert.AreEqual(amountRead, 0); }
/// <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) { // Parsing references include: // RFC1341 section 7: http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html // RFC2388: http://www.ietf.org/rfc/rfc2388.txt // First we need to read untill we find a boundary while (true) { string line = reader.ReadLine(); if (line == boundary) { break; } if (line == null) { throw new MultipartParseException("Could not find expected boundary"); } } // Now that we've found the initial boundary we know where to start. // We need parse each individual section while (!readEndBoundary) { // ParseSection will parse up to and including // the next boundary. ParseSection(reader); } }
/// <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 FilePart ParseFilePart(Dictionary <string, string> parameters, RebufferableBinaryReader reader) { // We want to create a stream and fill it with the data from the // file. var data = new MemoryStream(); var curBuffer = new byte[this.BinaryBufferSize]; var prevBuffer = new byte[this.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[this.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 = this.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); 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 FilePart(parameters["name"], parameters["filename"], data, contentType, contentDisposition); return(part); }
public void ReadLineReturnsNullOnNoData() { var reader = new RebufferableBinaryReader(new MemoryStream(new byte[6]), Encoding.UTF8); var s = reader.ReadLine(); Assert.AreEqual(s, "\0\0\0\0\0\0"); Assert.IsNull(reader.ReadLine()); }
/// <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) { // Our first job is to determine what type of section this is: form data or file. // This is a bit tricky because files can still be encoded with Content-Disposition: form-data // in the case of single file uploads. Multi-file uploads have Content-Disposition: file according // to the spec however in practise it seems that multiple files will be represented by // multiple Content-Disposition: form-data files. 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 == this.boundary || line == this.endBoundary) { throw new MultipartParseException("Unexpected end of section"); } // This line parses the header values into a set of key/value pairs. For example: // Content-Disposition: form-data; name="textdata" // ["content-disposition"] = "form-data" // ["name"] = "textdata" // Content-Disposition: form-data; name="file"; filename="data.txt" // ["content-disposition"] = "form-data" // ["name"] = "file" // ["filename"] = "data.txt" // Content-Type: text/plain // ["content-type"] = "text/plain" var values = SplitBySemicolonIgnoringSemicolonsInQuotes(line) .Select(x => x.Split(new[] { ':', '=' }, 2)) // Limit split to 2 splits so we don't accidently split characters in file paths. .ToDictionary( x => x[0].Trim().Replace("\"", string.Empty).ToLower(), x => x[1].Trim().Replace("\"", string.Empty)); // Here we just want to push all the values that we just retrieved into the // parameters dictionary. try { foreach (var pair in values) { parameters.Add(pair.Key, pair.Value); } } catch (ArgumentException) { throw new MultipartParseException("Duplicate field in section"); } line = reader.ReadLine(); } // Now that we've consumed all the parameters we're up to the body. We're going to do // different things depending on if we're parsing a, relatively small, form value or a // potentially large file. if (parameters.ContainsKey("filename")) { // Right now we assume that if a section contains filename then it is a file. // This assumption needs to be checked, it holds true in firefox but is untested for other // browsers. FilePart part = this.ParseFilePart(parameters, reader); this.Files.Add(part); } else { ParameterPart part = this.ParseParameterPart(parameters, reader); this.Parameters.Add(part.Name, part); } }
/// <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 ParameterPart ParseParameterPart(Dictionary<string, string> parameters, RebufferableBinaryReader reader) { // Our job is to get the actual "data" part of the parameter and construct // an actual ParameterPart object with it. All we need to do is read data into a string // untill we hit the boundary var data = new StringBuilder(); string line = reader.ReadLine(); while (line != this.boundary && line != this.endBoundary) { if (line == null) { throw new MultipartParseException("Unexpected end of section"); } data.Append(line); line = reader.ReadLine(); } if (line == this.endBoundary) { this.readEndBoundary = true; } // If we're here we've hit the boundary and have the data! var part = new ParameterPart(parameters["name"], data.ToString()); return part; }