Provides methods to interpret and read a stream as either character or binary data similar to a BinaryReader and provides the ability to push data onto the front of the stream.
Inheritance: IDisposable
Beispiel #1
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 == 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);
        }
Beispiel #3
0
        /// <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');
        }
Beispiel #5
0
        /// <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');
        }
Beispiel #11
0
        /// <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);
        }
Beispiel #12
0
        /// <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);
        }
Beispiel #13
0
        /// <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>
        ///     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);
            }
        }
Beispiel #22
0
        /// <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);
            }
        }
Beispiel #23
0
        /// <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);
            }
        }
Beispiel #28
0
        /// <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());
        }
Beispiel #30
0
        /// <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;
        }