Exemple #1
0
        /// <summary>Creates a <see cref="NameValuesCollection&lt;TValue&gt;"/> from a collection of key-value pairs.</summary>
        public static NameValuesCollection <TValue> ToNameValuesCollection <TValue>(this IEnumerable <KeyValuePair <string, TValue> > pairs)
        {
            var result = new NameValuesCollection <TValue>();

            foreach (var pair in pairs)
            {
                result[pair.Key].Add(pair.Value);
            }
            return(result);
        }
Exemple #2
0
 /// <summary>Initialises this HTTP request from the specified HTTP request.</summary>
 protected HttpRequest(HttpRequest copyFrom)
 {
     Url             = copyFrom.Url;
     _postFields     = copyFrom._postFields;
     _fileUploads    = copyFrom._fileUploads;
     HttpVersion     = copyFrom.HttpVersion;
     Method          = copyFrom.Method;
     Headers         = copyFrom.Headers;
     ClientIPAddress = copyFrom.ClientIPAddress;
     SourceIP        = copyFrom.SourceIP;
 }
Exemple #3
0
 /// <summary>
 /// Returns a read-only version of this collection. The returned collection could be the
 /// same as this one, if it's already read-only, or it could be a wrapper created around
 /// the items in this collection. If the original collection gets modified, the read-only
 /// version will reflect the changes instantly.
 /// </summary>
 public NameValuesCollection <TValue> AsReadOnly()
 {
     if (_isReadOnly)
     {
         return(this);
     }
     if (_asReadOnly == null)
     {
         _asReadOnly = new NameValuesCollection <TValue>(_items, true);
     }
     return(_asReadOnly);
 }
Exemple #4
0
        /// <summary>If this request is a POST/PUT/PATCH request, replaces the body of the request with data from the specified stream.
        /// This will clear and reinitialize all the parameter values and file uploads.</summary>
        /// <param name="body">Stream to read new request body from.</param>
        /// <param name="tempPath">The temporary directory to use for file uploads. Default is <see cref="Path.GetTempPath"/>.</param>
        /// <param name="storeFileUploadInFileAtSize">The maximum size (in bytes) at which file uploads are stored in memory.
        /// Any uploads that exceed this limit are written to temporary files on disk. Default is 16 MB.</param>
        internal void ParsePostBody(Stream body, string tempPath = null, long storeFileUploadInFileAtSize = 16 * 1024 * 1024)
        {
            _fileUploads.Clear();
            _postFields.Clear();

            if (Method != HttpMethod.Post)
            {
                return;
            }

            if (Headers.ContentType == HttpPostContentType.ApplicationXWwwFormUrlEncoded)
            {
                using (var reader = new StreamReader(body, Encoding.UTF8))
                    _postFields = HttpHelper.ParseQueryValueParameters(reader).ToNameValuesCollection();
                return;
            }

            // An excessively long boundary is going to screw up the following algorithm.
            // (Actually a limit of up to bufferSize - 8 would work, but I think 1024 is more than enough.)
            if (body == null || Headers.ContentMultipartBoundary == null || Headers.ContentMultipartBoundary.Length > 1024)
            {
                return;
            }

            if (tempPath == null)
            {
                tempPath = Path.GetTempPath();
            }

            // Instead of reallocating a new buffer multiple times, allocate at most two buffers and switch between them as necessary
            int bufferSize = 65536;

            byte[] buffer1 = new byte[bufferSize];
            byte[] buffer2 = null;
            byte[] buffer  = buffer1;
            void switchBuffer(int offset, int count)
            {
                if (buffer == buffer1)
                {
                    if (buffer2 == null)
                    {
                        buffer2 = new byte[bufferSize];
                    }
                    Buffer.BlockCopy(buffer, offset, buffer2, 0, count);
                    buffer = buffer2;
                }
                else
                {
                    Buffer.BlockCopy(buffer, offset, buffer1, 0, count);
                    buffer = buffer1;
                }
            }

            // Process request body
            int bytesRead = body.Read(buffer, 0, bufferSize);

            if (bytesRead == 0)    // premature end of request body
            {
                return;
            }

            // We expect the input to begin with "--" followed by the boundary followed by "\r\n"
            // It is, however, allowed to have CRLFs before the first "--"
            var crlfs = 0;

            while (crlfs + 1 < bytesRead && buffer[crlfs] == '\r' && buffer[crlfs + 1] == '\n')
            {
                crlfs += 2;
            }
            byte[] expecting   = ("--" + Headers.ContentMultipartBoundary + "\r\n").ToUtf8();
            int    bufferIndex = bytesRead;

            while (bufferIndex < buffer.Length && bufferIndex < expecting.Length + crlfs)
            {
                bytesRead = body.Read(buffer, bufferIndex, buffer.Length - bufferIndex);
                if (bytesRead == 0)    // premature end of request body
                {
                    return;
                }
                bufferIndex += bytesRead;
                while (crlfs + 1 < bufferIndex && buffer[crlfs] == '\r' && buffer[crlfs + 1] == '\n')
                {
                    crlfs += 2;
                }
                if (expecting.Length + crlfs > buffer.Length)   // Sanity check in case the client tries to fill the buffer with just CRLFs
                {
                    return;
                }
            }
            if (!buffer.SubarrayEquals(crlfs, expecting, 0, expecting.Length))
            {
                return;
            }
            bytesRead   = bufferIndex - expecting.Length - crlfs;
            bufferIndex = expecting.Length + crlfs;

            // Now comes the main reading loop
            bool    processingHeaders             = true;
            string  currentHeaders                = "";
            string  currentFieldName              = null;
            Stream  currentWritingStream          = null;
            bool    currentIsFileUpload           = false;
            string  currentFileUploadFilename     = null;
            string  currentFileUploadContentType  = null;
            string  currentFileUploadTempFilename = null;
            Decoder utf8Decoder = Encoding.UTF8.GetDecoder();

            char[] chArr                    = new char[1];
            byte[] lastBoundary             = ("\r\n--" + Headers.ContentMultipartBoundary + "--\r\n").ToUtf8();
            byte[] middleBoundary           = ("\r\n--" + Headers.ContentMultipartBoundary + "\r\n").ToUtf8();
            var    inMemoryFileUploads      = new SortedList <long, List <FileUpload> >();
            long   inMemoryFileUploadsTotal = 0;

            while (bufferIndex > 0 || bytesRead > 0)
            {
                int writeIndex = 0;
                if (bytesRead > 0)
                {
                    if (processingHeaders)
                    {
                        bool newLineFound = false;
                        while (!newLineFound && bytesRead > 0)
                        {
                            int numCh = utf8Decoder.GetChars(buffer, bufferIndex, 1, chArr, 0);
                            bufferIndex++;
                            bytesRead--;
                            if (numCh != 0)
                            {
                                currentHeaders += chArr[0];
                            }
                            newLineFound = currentHeaders.EndsWith("\r\n\r\n");
                        }

                        if (newLineFound)
                        {
                            currentIsFileUpload           = false;
                            currentFileUploadContentType  = null;
                            currentFileUploadFilename     = null;
                            currentFileUploadTempFilename = null;
                            currentFieldName     = null;
                            currentWritingStream = null;
                            foreach (string header in currentHeaders.Split(new string[] { "\r\n" }, StringSplitOptions.None))
                            {
                                Match m;
                                if ((m = Regex.Match(header, @"^content-disposition\s*:\s*(?:form-data|file)\s*;(.*)$", RegexOptions.IgnoreCase)).Success)
                                {
                                    string v = m.Groups[1].Value;
                                    while (v.Length > 0)
                                    {
                                        m = Regex.Match(v, @"^\s*(\w+)=""([^""]*)""\s*(?:;\s*|$)");
                                        if (!m.Success)
                                        {
                                            m = Regex.Match(v, @"^\s*(\w+)=([^;]*)\s*(?:;\s*|$)");
                                        }
                                        if (!m.Success)
                                        {
                                            break;
                                        }
                                        if (m.Groups[1].Value.ToLowerInvariant() == "name")
                                        {
                                            currentFieldName = m.Groups[2].Value;
                                        }
                                        else if (m.Groups[1].Value.ToLowerInvariant() == "filename")
                                        {
                                            currentFileUploadFilename = m.Groups[2].Value;
                                        }
                                        v = v.Substring(m.Length);
                                    }
                                }
                                else if ((m = Regex.Match(header, @"^content-type\s*:\s*(.*)$", RegexOptions.IgnoreCase)).Success)
                                {
                                    currentFileUploadContentType = m.Groups[1].Value;
                                }
                            }
                            if (currentFieldName != null)
                            {
                                currentWritingStream = new MemoryStream();
                                if (currentFileUploadFilename != null)
                                {
                                    currentIsFileUpload = true;
                                }
                            }
                            processingHeaders = false;
                            continue;
                        }
                    }
                    else if (bytesRead >= lastBoundary.Length)   // processing content
                    {
                        bool boundaryFound = false;
                        bool end           = false;

                        int boundaryIndex = buffer.IndexOfSubarray(lastBoundary, bufferIndex, bytesRead);
                        if (boundaryIndex != -1)
                        {
                            boundaryFound = true;
                            end           = true;
                        }
                        int middleBoundaryIndex = buffer.IndexOfSubarray(middleBoundary, bufferIndex, bytesRead);
                        if (middleBoundaryIndex != -1 && (!boundaryFound || middleBoundaryIndex < boundaryIndex))
                        {
                            boundaryFound = true;
                            boundaryIndex = middleBoundaryIndex;
                            end           = false;
                        }

                        int howMuchToWrite = boundaryFound
                                                               // If we have encountered the boundary, write all the data up to it
                            ? boundaryIndex - bufferIndex
                                                               // Write as much of the data to the output stream as possible, but leave enough so that we can still recognise the boundary
                            : bytesRead - lastBoundary.Length; // this is never negative because of the "if" we're in

                        // Write the aforementioned amount of data to the output stream
                        if (howMuchToWrite > 0 && currentWritingStream != null)
                        {
                            // If we're currently processing a file upload in memory, and it takes the total file uploads over the limit...
                            if (currentIsFileUpload && currentWritingStream is MemoryStream && ((MemoryStream)currentWritingStream).Length + inMemoryFileUploadsTotal + howMuchToWrite > storeFileUploadInFileAtSize)
                            {
                                var memory       = (MemoryStream)currentWritingStream;
                                var inMemoryKeys = inMemoryFileUploads.Keys;
                                if (inMemoryKeys.Count > 0 && memory.Length < inMemoryKeys[inMemoryKeys.Count - 1])
                                {
                                    // ... switch the largest one to a temporary file
                                    var lastKey       = inMemoryKeys[inMemoryKeys.Count - 1];
                                    var biggestUpload = inMemoryFileUploads[lastKey][0];
                                    inMemoryFileUploads[lastKey].RemoveAt(0);
                                    biggestUpload.LocalFilename = HttpInternalObjects.RandomTempFilepath(tempPath, out var fileStream);
                                    fileStream.Write(biggestUpload.Data, 0, biggestUpload.Data.Length);
                                    fileStream.Close();
                                    fileStream.Dispose();
                                    inMemoryFileUploadsTotal -= biggestUpload.Data.LongLength;
                                    biggestUpload.Data        = null;
                                    if (inMemoryFileUploads[lastKey].Count == 0)
                                    {
                                        inMemoryFileUploads.Remove(lastKey);
                                    }
                                }
                                else
                                {
                                    // ... switch this one to a temporary file
                                    currentFileUploadTempFilename = HttpInternalObjects.RandomTempFilepath(tempPath, out currentWritingStream);
                                    memory.WriteTo(currentWritingStream);
                                    memory.Close();
                                    memory.Dispose();
                                }
                            }
                            currentWritingStream.Write(buffer, bufferIndex, howMuchToWrite);
                        }

                        // If we encountered the boundary, add this field to _postFields or this upload to _fileUploads or inMemoryFileUploads
                        if (boundaryFound)
                        {
                            if (currentWritingStream != null)
                            {
                                currentWritingStream.Close();

                                if (!currentIsFileUpload)
                                {
                                    // It's a normal field
                                    _postFields[currentFieldName].Add(Encoding.UTF8.GetString(((MemoryStream)currentWritingStream).ToArray()));
                                }
                                else
                                {
                                    // It's a file upload
                                    var fileUpload = new FileUpload(currentFileUploadContentType, currentFileUploadFilename);
                                    if (currentFileUploadTempFilename != null)
                                    {
                                        // The file upload has already been written to disk
                                        fileUpload.LocalFilename = currentFileUploadTempFilename;
                                    }
                                    else
                                    {
                                        // The file upload is still in memory. Keep track of it in inMemoryFileUploads so that we can still write it to disk later if necessary
                                        var memory = (MemoryStream)currentWritingStream;
                                        fileUpload.Data = memory.ToArray();
                                        inMemoryFileUploads.AddSafe(fileUpload.Data.LongLength, fileUpload);
                                        inMemoryFileUploadsTotal += fileUpload.Data.LongLength;
                                    }
                                    _fileUploads[currentFieldName] = fileUpload;
                                }

                                currentWritingStream.Dispose();
                                currentWritingStream = null;
                            }

                            // If that was the final boundary, we are done
                            if (end)
                            {
                                break;
                            }

                            // Consume the boundary and go back to processing headers
                            bytesRead        -= boundaryIndex - bufferIndex + middleBoundary.Length;
                            bufferIndex       = boundaryIndex + middleBoundary.Length;
                            processingHeaders = true;
                            currentHeaders    = "";
                            utf8Decoder.Reset();
                            continue;
                        }
                        else
                        {
                            // No boundary there. Received data has been written to the currentWritingStream above.
                            // Now copy the remaining little bit (which may contain part of the bounary) into a new buffer
                            switchBuffer(bufferIndex + howMuchToWrite, bytesRead - howMuchToWrite);
                            bytesRead -= howMuchToWrite;
                            writeIndex = bytesRead;
                        }
                    }
                    else if (bufferIndex > 0)
                    {
                        // We are processing content, but there is not enough data in the buffer to ensure that it doesn't contain part of the boundary.
                        // Therefore, just copy the data to a new buffer and continue receiving more
                        switchBuffer(bufferIndex, bytesRead);
                        writeIndex = bytesRead;
                    }
                }
                bufferIndex = 0;
                // We need to read enough data to contain the boundary
                do
                {
                    bytesRead = body.Read(buffer, writeIndex, bufferSize - writeIndex);
                    if (bytesRead == 0)
                    {
                        // Premature end of content. We want to allow broken clients (such as UnityWebRequest) to work, so tolerate this
                        if (currentWritingStream != null)
                        {
                            currentWritingStream.Write(buffer, 0, writeIndex);
                            currentWritingStream.Close();

                            if (!currentIsFileUpload)
                            {
                                // It's a normal field
                                _postFields[currentFieldName].Add(Encoding.UTF8.GetString(((MemoryStream)currentWritingStream).ToArray()));
                            }
                            else
                            {
                                // It's a file upload
                                var fileUpload = new FileUpload(currentFileUploadContentType, currentFileUploadFilename);
                                if (currentFileUploadTempFilename != null)
                                {
                                    fileUpload.LocalFilename = currentFileUploadTempFilename;
                                }
                                else
                                {
                                    fileUpload.Data = ((MemoryStream)currentWritingStream).ToArray();
                                }
                                _fileUploads[currentFieldName] = fileUpload;
                            }

                            currentWritingStream.Dispose();
                        }

                        return;
                    }
                    writeIndex += bytesRead;
                }while (writeIndex < lastBoundary.Length);
                bytesRead = writeIndex;
            }
        }